From b854c57aa42962a6e6637b5386e71e290854554d Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 16 Nov 2020 15:55:07 +0800 Subject: [PATCH 1/9] fix build warnings --- app/src/main/java/com/geeksville/mesh/service/SimRadio.kt | 2 +- app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/SimRadio.kt b/app/src/main/java/com/geeksville/mesh/service/SimRadio.kt index 883308445..36b87b40d 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SimRadio.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SimRadio.kt @@ -38,7 +38,7 @@ class SimRadio(private val context: Context) { }.build().toByteArray() ) */ - simInitPackets.forEach { json -> + simInitPackets.forEach { _ -> val fromRadio = MeshProtos.FromRadio.newBuilder().apply { packet = MeshProtos.MeshPacket.newBuilder().apply { // jsonParser.merge(json, this) 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 a18c4d085..c7c401db6 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -802,7 +802,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { debug("We have location access") } - locationSettingsResponse.addOnFailureListener { exception -> + locationSettingsResponse.addOnFailureListener { _ -> errormsg("Failed to get location access") // We always show the toast regardless of what type of exception we receive. Because even non // resolvable api exceptions mean user still needs to fix something. From 31e0136b2adc5cf93eb22c5c35c5c6fc8343bdfc Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 16 Nov 2020 15:57:40 +0800 Subject: [PATCH 2/9] support spiffs updates over OTA (not yet tested) for https://github.com/meshtastic/Meshtastic-device/issues/496 --- .../geeksville/mesh/service/BLEException.kt | 5 +- .../mesh/service/BluetoothInterface.kt | 2 +- .../geeksville/mesh/service/MeshService.kt | 2 +- .../mesh/service/SoftwareUpdateService.kt | 102 ++++++++++++++---- 4 files changed, 90 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/BLEException.kt b/app/src/main/java/com/geeksville/mesh/service/BLEException.kt index 657aaf963..4ee38cd1b 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BLEException.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BLEException.kt @@ -1,5 +1,8 @@ package com.geeksville.mesh.service import java.io.IOException +import java.util.* -open class BLEException(msg: String) : IOException(msg) \ No newline at end of file +open class BLEException(msg: String) : IOException(msg) + +open class BLECharacteristicNotFoundException(uuid: UUID) : BLEException("Can't get characteristic $uuid") \ No newline at end of file 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 5f4d2bb45..a1e1eda66 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt @@ -489,6 +489,6 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String * Get a chracteristic, but in a safe manner because some buggy BLE implementations might return null */ private fun getCharacteristic(uuid: UUID) = - bservice.getCharacteristic(uuid) ?: throw BLEException("Can't get characteristic $uuid") + bservice.getCharacteristic(uuid) ?: throw BLECharacteristicNotFoundException(uuid) } 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 2cd528cf3..8e7cfab81 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1334,7 +1334,7 @@ class MeshService : Service(), Logging { return 0 // We don't have mynodeinfo yet, so just let the radio eventually assign an ID } - var firmwareUpdateFilename: String? = null + var firmwareUpdateFilename: UpdateFilenames? = null /*** * Return the filename we will install on the device 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 1c955064f..88d3beb6b 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt @@ -18,7 +18,12 @@ import java.util.zip.CRC32 */ fun toNetworkByteArray(value: Int, formatType: Int): ByteArray { - val len: Int = 4 // getTypeLen(formatType) + val len = when (formatType) { + BluetoothGattCharacteristic.FORMAT_UINT8 -> 1 + BluetoothGattCharacteristic.FORMAT_UINT32 -> 4 + else -> TODO() + } + val mValue = ByteArray(len) when (formatType) { @@ -44,6 +49,9 @@ fun toNetworkByteArray(value: Int, formatType: Int): ByteArray { mValue.get(offset++) = (value shr 16 and 0xFF).toByte() mValue.get(offset) = (value shr 24 and 0xFF).toByte() } */ + BluetoothGattCharacteristic.FORMAT_UINT8 -> + mValue[0] = (value and 0xFF).toByte() + BluetoothGattCharacteristic.FORMAT_UINT32 -> { mValue[0] = (value and 0xFF).toByte() mValue[1] = (value shr 8 and 0xFF).toByte() @@ -55,6 +63,9 @@ fun toNetworkByteArray(value: Int, formatType: Int): ByteArray { return mValue } + +data class UpdateFilenames(val appLoad: String?, val spiffs: String?) + /** * typical flow * @@ -152,6 +163,8 @@ class SoftwareUpdateService : JobIntentService(), Logging { UUID.fromString("4826129c-c22a-43a3-b066-ce8f0d5bacc6") // write crc32, write last - writing this will complete the OTA operation, now you can read result private val SW_UPDATE_RESULT_CHARACTER = UUID.fromString("5e134862-7411-4424-ac4a-210937432c77") // read|notify result code, readable but will notify when the OTA operation completes + private val SW_UPDATE_REGION_CHARACTER = + UUID.fromString("5e134862-7411-4424-ac4a-210937432c67") // write - used to set the region we are setting (appload vs spiffs) private val SW_VERSION_CHARACTER = longBLEUUID("2a28") private val MANUFACTURE_CHARACTER = longBLEUUID("2a29") @@ -210,28 +223,36 @@ class SoftwareUpdateService : JobIntentService(), Logging { false // If we fail parsing our update info } - /** Return the filename this device needs to use as an update (or null if no update needed) + /** Return a Pair of apploadfilename, spiffs filename this device needs to use as an update (or null if no update needed) */ fun getUpdateFilename( context: Context, mfg: String - ): String? { + ): UpdateFilenames { val curver = context.getString(R.string.cur_firmware_version) - val base = "firmware-$mfg-$curver.bin" - // Check to see if the file exists (some builds might not include update files for size reasons) val firmwareFiles = context.assets.list("firmware") ?: arrayOf() - return if (firmwareFiles.contains(base)) - "firmware/$base" - else - null + + val appLoad = "firmware-$mfg-$curver.bin" + val spiffs = "spiffs-$curver.bin" + + return UpdateFilenames( + if (firmwareFiles.contains(appLoad)) + "firmware/$appLoad" + else + null, + if (firmwareFiles.contains(spiffs)) + "firmware/$spiffs" + else + null + ) } /** Return the filename this device needs to use as an update (or null if no update needed) * No longer used, because we get update info inband from our radio API */ - fun getUpdateFilename(context: Context, sync: SafeBluetooth): String? { + fun getUpdateFilename(context: Context, sync: SafeBluetooth): UpdateFilenames? { val service = sync.gatt!!.services.find { it.uuid == SW_UPDATE_UUID }!! //val hwVerDesc = service.getCharacteristic(HW_VERSION_CHARACTER) @@ -248,19 +269,66 @@ class SoftwareUpdateService : JobIntentService(), Logging { * A public function so that if you have your own SafeBluetooth connection already open * you can use it for the software update. */ - fun doUpdate(context: Context, sync: SafeBluetooth, assetName: String) { + fun doUpdate(context: Context, sync: SafeBluetooth, assets: UpdateFilenames) { + // we must attempt spiffs first, because if we update the appload the device will reboot afterwards + try { + assets.spiffs?.let { doUpdate(context, sync, it, FLASH_REGION_SPIFFS) } + } + catch(_: BLECharacteristicNotFoundException) { + // If we can't update spiffs (because not supported by target), do not fail + errormsg("Ignoring failure to update spiffs on old appload") + } + assets.appLoad?.let { doUpdate(context, sync, it, FLASH_REGION_APPLOAD) } + progress = -1 // success + } + + // writable region codes in the ESP32 update code + private val FLASH_REGION_APPLOAD = 0 + private val FLASH_REGION_SPIFFS = 100 + + /** + * A public function so that if you have your own SafeBluetooth connection already open + * you can use it for the software update. + */ + private fun doUpdate(context: Context, sync: SafeBluetooth, assetName: String, flashRegion: Int = FLASH_REGION_APPLOAD) { try { val g = sync.gatt!! val service = g.services.find { it.uuid == SW_UPDATE_UUID } ?: throw BLEException("Couldn't find update service") - info("Starting firmware update for $assetName") + /** + * Get a chracteristic, but in a safe manner because some buggy BLE implementations might return null + */ + fun getCharacteristic(uuid: UUID) = + service.getCharacteristic(uuid) + ?: throw BLECharacteristicNotFoundException(uuid) + + info("Starting firmware update for $assetName, flash region $flashRegion") progress = 0 - val totalSizeDesc = service.getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER) - val dataDesc = service.getCharacteristic(SW_UPDATE_DATA_CHARACTER) - val crc32Desc = service.getCharacteristic(SW_UPDATE_CRC32_CHARACTER) - val updateResultDesc = service.getCharacteristic(SW_UPDATE_RESULT_CHARACTER) + val totalSizeDesc = getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER) + val dataDesc = getCharacteristic(SW_UPDATE_DATA_CHARACTER) + val crc32Desc = getCharacteristic(SW_UPDATE_CRC32_CHARACTER) + val updateResultDesc = getCharacteristic(SW_UPDATE_RESULT_CHARACTER) + + /// Try to set the destination region for programming (spiffs vs appload etc) + /// Old apploads don't have this feature, but we only fail if the user was trying to set a + /// spiffs - otherwise we assume appload. + try { + val updateRegionDesc = getCharacteristic(SW_UPDATE_REGION_CHARACTER) + sync.writeCharacteristic( + updateRegionDesc, + toNetworkByteArray(flashRegion, BluetoothGattCharacteristic.FORMAT_UINT8) + ) + } + catch(ex: BLECharacteristicNotFoundException) { + errormsg("Can't set flash programming region (old appload?") + if(flashRegion != FLASH_REGION_APPLOAD) { + errormsg("Can't set flash programming region (old appload?)") + throw ex + } + warn("Ignoring setting appload flashRegion (old appload?") + } context.assets.open(assetName).use { firmwareStream -> val firmwareCrc = CRC32() @@ -320,8 +388,6 @@ class SoftwareUpdateService : JobIntentService(), Logging { // 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) } - - progress = -1 // success } } catch (ex: BLEException) { progress = -3 From 88b1ad5443f4ae38a5470d29623aeb35d3d28e5b Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 17 Nov 2020 19:54:48 +0800 Subject: [PATCH 3/9] remove support for 0.8 version URLs (found while investigating #188) --- app/src/main/java/com/geeksville/mesh/model/Channel.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 7cd889554..e698dbccb 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Channel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Channel.kt @@ -31,17 +31,17 @@ data class Channel( MeshProtos.ChannelSettings.newBuilder().setName(defaultChannelName) .setModemConfig(MeshProtos.ChannelSettings.ModemConfig.Bw125Cr45Sf128).build() ) - - private const val prefixRoot = "https://www.meshtastic.org/c/" - const val prefix = "$prefixRoot#" + + const val prefix = "https://www.meshtastic.org/c/#" private const val base64Flags = Base64.URL_SAFE + Base64.NO_WRAP private fun urlToSettings(url: Uri): MeshProtos.ChannelSettings { val urlStr = url.toString() - // 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#?(.*)") + // We no longer support the super old (about 0.8ish? verison of the URLs that don't use the # separator - I doubt + // anyone is still using that old format + val pathRegex = Regex("$prefix(.*)") val (base64) = pathRegex.find(urlStr)?.destructured ?: throw MalformedURLException("Not a meshtastic URL: ${urlStr.take(40)}") val bytes = Base64.decode(base64, base64Flags) From 6e2cb723ea2d5c9bf20581f3f841193f1bd2e910 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 20 Nov 2020 07:51:25 +0800 Subject: [PATCH 4/9] 1.1.9 spiffs firmware update seems to work --- app/build.gradle | 6 +++--- .../com/geeksville/mesh/service/SoftwareUpdateService.kt | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9e3aa28ca..8341bb9d9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,13 +25,13 @@ android { } } */ compileSdkVersion 29 - buildToolsVersion "30.0.1" // Note: 30.0.0.2 doesn't yet work on Github actions CI + buildToolsVersion "30.0.2" // 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) targetSdkVersion 29 - versionCode 20108 // format is Mmmss (where M is 1+the numeric major number - versionName "1.1.8" + versionCode 20109 // format is Mmmss (where M is 1+the numeric major number + versionName "1.1.9" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { 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 88d3beb6b..5a4fe4b39 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt @@ -349,7 +349,11 @@ class SoftwareUpdateService : JobIntentService(), Logging { // Send all the blocks while (firmwareNumSent < firmwareSize) { - progress = firmwareNumSent * 100 / firmwareSize + // If we are doing the spiffs partition, we limit progress to a max of 50%, so that the user doesn't think we are done + // yet + val maxProgress = if(flashRegion != FLASH_REGION_APPLOAD) + 50 else 100 + progress = firmwareNumSent * maxProgress / firmwareSize debug("sending block ${progress}%") var blockSize = 512 - 3 // Max size MTU excluding framing From 5007019a2e858998954a7ede81825081c66b7330 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 21 Nov 2020 08:41:26 +0800 Subject: [PATCH 5/9] fix #204 - throw error if sent message is too long --- app/src/main/java/com/geeksville/mesh/service/MeshService.kt | 5 +++++ app/src/main/proto | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) 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 8e7cfab81..0c54748b1 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1458,6 +1458,11 @@ class MeshService : Service(), Logging { // Keep a record of datapackets, so GUIs can show proper chat history rememberDataPacket(p) + if(p.bytes.size >= MeshProtos.Constants.DATA_PAYLOAD_LEN.number) { + p.status = MessageStatus.ERROR + throw RemoteException("Message too long") + } + if (p.id != 0) { // If we have an ID we can wait for an ack or nak deleteOldPackets() sentPackets[p.id] = p diff --git a/app/src/main/proto b/app/src/main/proto index a0b8d8889..a36b31a43 160000 --- a/app/src/main/proto +++ b/app/src/main/proto @@ -1 +1 @@ -Subproject commit a0b8d888961720d70ab7467c94d8fa0687e58020 +Subproject commit a36b31a43c53cab8e62ba04dfbb1fdcc9ecfe821 From 663ac774de5fc5040f2760a2dcaeff43ad126103 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 28 Nov 2020 08:24:32 +0800 Subject: [PATCH 6/9] update libs --- app/build.gradle | 4 ++-- build.gradle | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8341bb9d9..81a005e77 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -147,7 +147,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" // For now I'm not using javalite, because I want JSON printing - implementation ('com.google.protobuf:protobuf-java:3.13.0') + implementation ('com.google.protobuf:protobuf-java:3.14.0') // For UART access // implementation 'com.google.android.things:androidthings:1.0' @@ -177,7 +177,7 @@ dependencies { // barcode support // per https://github.com/journeyapps/zxing-android-embedded for support of android version 22 implementation('com.journeyapps:zxing-android-embedded:4.1.0') { transitive = false } - implementation 'com.google.zxing:core:3.4.0' + implementation 'com.google.zxing:core:3.4.1' def work_version = '2.4.0' diff --git a/build.gradle b/build.gradle index 443c5418e..f37671e92 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.4.10' + ext.kotlin_version = '1.4.20' ext.coroutines_version = "1.3.9" repositories { @@ -21,10 +21,10 @@ buildscript { // Check that you have the Google Services Gradle plugin v4.3.2 or later // (if not, add it). classpath 'com.google.gms:google-services:4.3.4' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1' // protobuf plugin - docs here https://github.com/google/protobuf-gradle-plugin - classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.12' + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.14' //classpath "app.brant:amazonappstorepublisher:0.1.0" classpath 'com.github.triplet.gradle:play-publisher:2.8.0' From cc2b99fdfcf09ac3be4e0930f94dec8fbec13554 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 7 Dec 2020 19:50:06 +0800 Subject: [PATCH 7/9] WIP for new protobufs --- app/build.gradle | 6 ++-- app/src/main/AndroidManifest.xml | 2 +- .../java/com/geeksville/mesh/DataPacket.kt | 6 ++-- .../java/com/geeksville/mesh/MainActivity.kt | 6 ++-- .../geeksville/mesh/service/MeshService.kt | 36 +++++++++++-------- app/src/main/proto | 2 +- geeksville-androidlib | 2 +- 7 files changed, 34 insertions(+), 26 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 81a005e77..e83225dfd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,8 +30,8 @@ android { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) targetSdkVersion 29 - versionCode 20109 // format is Mmmss (where M is 1+the numeric major number - versionName "1.1.9" + versionCode 20120 // format is Mmmss (where M is 1+the numeric major number + versionName "1.1.20" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -166,7 +166,7 @@ dependencies { implementation 'com.google.android.gms:play-services-auth:19.0.0' // Add the Firebase SDK for Crashlytics. - implementation 'com.google.firebase:firebase-crashlytics:17.2.2' + implementation 'com.google.firebase:firebase-crashlytics:17.3.0' // alas implementation bug deep in the bowels when I tried it for my SyncBluetoothDevice class // implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5422c73bc..69e08be69 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -133,7 +133,7 @@ diff --git a/app/src/main/java/com/geeksville/mesh/DataPacket.kt b/app/src/main/java/com/geeksville/mesh/DataPacket.kt index df8e3adfc..96ce62a21 100644 --- a/app/src/main/java/com/geeksville/mesh/DataPacket.kt +++ b/app/src/main/java/com/geeksville/mesh/DataPacket.kt @@ -22,7 +22,7 @@ enum class MessageStatus : Parcelable { data class DataPacket( var to: String? = ID_BROADCAST, // a nodeID string, or ID_BROADCAST for broadcast val bytes: ByteArray?, - val dataType: Int, // A value such as MeshProtos.Data.Type.OPAQUE_VALUE + val dataType: Int, // A port number for this packet (formerly called DataType, see portnums.proto for new usage instructions) var from: String? = ID_LOCAL, // a nodeID string, or ID_LOCAL for localhost var time: Long = System.currentTimeMillis(), // msecs since 1970 var id: Int = 0, // 0 means unassigned @@ -39,14 +39,14 @@ data class DataPacket( */ constructor(to: String? = ID_BROADCAST, text: String) : this( to, text.toByteArray(utf8), - MeshProtos.Data.Type.CLEAR_TEXT_VALUE + Portnums.PortNum.TEXT_MESSAGE_APP_VALUE ) /** * If this is a text message, return the string, otherwise null */ val text: String? - get() = if (dataType == MeshProtos.Data.Type.CLEAR_TEXT_VALUE) + get() = if (dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) bytes?.toString(utf8) else null diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index ff77040c4..555a887e3 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -324,14 +324,14 @@ class MainActivity : AppCompatActivity(), Logging, DataPacket( "+16508675310", testPayload, - MeshProtos.Data.Type.OPAQUE_VALUE + Portnums.PortNum.PRIVATE_APP_VALUE ) ) m.send( DataPacket( "+16508675310", testPayload, - MeshProtos.Data.Type.CLEAR_TEXT_VALUE + Portnums.PortNum.TEXT_MESSAGE_APP_VALUE ) ) } @@ -707,7 +707,7 @@ class MainActivity : AppCompatActivity(), Logging, intent.getParcelableExtra(EXTRA_PAYLOAD)!! when (payload.dataType) { - MeshProtos.Data.Type.CLEAR_TEXT_VALUE -> { + Portnums.PortNum.TEXT_MESSAGE_APP_VALUE -> { model.messagesState.addMessage(payload) } else -> 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 0c54748b1..ef983d06d 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -547,7 +547,7 @@ class MeshService : Service(), Logging { to = toId, time = rxTime * 1000L, id = packet.id, - dataType = data.typValue, + dataType = data.portnumValue, bytes = bytes ) } @@ -558,7 +558,7 @@ class MeshService : Service(), Logging { private fun toMeshPacket(p: DataPacket): MeshPacket { return buildMeshPacket(p.to!!, id = p.id, wantAck = true) { data = MeshProtos.Data.newBuilder().also { - it.typ = MeshProtos.Data.Type.forNumber(p.dataType) + it.portnumValue = p.dataType it.payload = ByteString.copyFrom(p.bytes) }.build() } @@ -596,25 +596,33 @@ class MeshService : Service(), Logging { dataPacket.status = MessageStatus.RECEIVED rememberDataPacket(dataPacket) - when (data.typValue) { - MeshProtos.Data.Type.CLEAR_TEXT_VALUE -> { + when (data.portnumValue) { + Portnums.PortNum.TEXT_MESSAGE_APP_VALUE -> { debug("Received CLEAR_TEXT from $fromId") recentReceivedTextPacket = dataPacket updateNotification() - serviceBroadcasts.broadcastReceivedData(dataPacket) } - MeshProtos.Data.Type.CLEAR_READACK_VALUE -> - warn( - "TODO ignoring CLEAR_READACK from $fromId" - ) + // Handle new style position info + Portnums.PortNum.POSITION_APP_VALUE -> { + val rxTime = if (packet.rxTime != 0) packet.rxTime else currentSecond() + val u = MeshProtos.Position.parseFrom(data.payload) + handleReceivedPosition(packet.from, u, rxTime) + } - MeshProtos.Data.Type.OPAQUE_VALUE -> - serviceBroadcasts.broadcastReceivedData(dataPacket) + // Handle new style user info + Portnums.PortNum.NODEINFO_APP_VALUE -> { + val u = MeshProtos.User.parseFrom(data.payload) + handleReceivedUser(packet.from, u) + } - else -> TODO() - } + else -> { + debug("Received other data packet") + }} + + // We always tell other apps when new data packets arrive + serviceBroadcasts.broadcastReceivedData(dataPacket) GeeksvilleApplication.analytics.track( "num_data_receive", @@ -624,7 +632,7 @@ class MeshService : Service(), Logging { GeeksvilleApplication.analytics.track( "data_receive", DataPair("num_bytes", bytes.size), - DataPair("type", data.typValue) + DataPair("type", data.portnumValue) ) } } diff --git a/app/src/main/proto b/app/src/main/proto index a36b31a43..aac0044b2 160000 --- a/app/src/main/proto +++ b/app/src/main/proto @@ -1 +1 @@ -Subproject commit a36b31a43c53cab8e62ba04dfbb1fdcc9ecfe821 +Subproject commit aac0044b2dcca5daa86c6532c1d8c43475956d31 diff --git a/geeksville-androidlib b/geeksville-androidlib index af1a758b0..534f0e192 160000 --- a/geeksville-androidlib +++ b/geeksville-androidlib @@ -1 +1 @@ -Subproject commit af1a758b0d4ed0b98e412d0aa03195d30f95127a +Subproject commit 534f0e192bbbaaa6c32a981534b00451ed708ddc From 2e30dbcdd0768a057b418b5a22f3dfd6a409158a Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 7 Dec 2020 20:33:29 +0800 Subject: [PATCH 8/9] switching away from kotlin-android-extensions --- app/build.gradle | 8 +- .../java/com/geeksville/mesh/DataPacket.kt | 2 +- .../java/com/geeksville/mesh/MainActivity.kt | 16 +-- .../main/java/com/geeksville/mesh/NodeInfo.kt | 2 +- .../geeksville/mesh/service/MeshService.kt | 9 +- .../com/geeksville/mesh/ui/ChannelFragment.kt | 59 +++++----- .../com/geeksville/mesh/ui/DebugFragment.kt | 102 +++++++++--------- .../geeksville/mesh/ui/MessagesFragment.kt | 33 +++--- .../geeksville/mesh/ui/SettingsFragment.kt | 101 ++++++++--------- .../com/geeksville/mesh/ui/UsersFragment.kt | 29 ++--- 10 files changed, 191 insertions(+), 170 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e83225dfd..dfe11bfc4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,6 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-parcelize' apply plugin: 'kotlinx-serialization' apply plugin: 'com.google.gms.google-services' apply plugin: 'com.github.triplet.play' @@ -59,6 +59,8 @@ android { buildFeatures { // Enables Jetpack Compose for this module // compose true // NOTE, if true main app crashes if you use regular view layout functions + + viewBinding true } // Set both the Java and Kotlin compilers to target Java 8. @@ -85,10 +87,6 @@ play { serviceAccountCredentials = file("../../play-credentials.json") } -androidExtensions { - experimental = true -} - // per protobuf-gradle-plugin docs, this is recommended for android protobuf { protoc { diff --git a/app/src/main/java/com/geeksville/mesh/DataPacket.kt b/app/src/main/java/com/geeksville/mesh/DataPacket.kt index 96ce62a21..1b23b7d4c 100644 --- a/app/src/main/java/com/geeksville/mesh/DataPacket.kt +++ b/app/src/main/java/com/geeksville/mesh/DataPacket.kt @@ -2,7 +2,7 @@ package com.geeksville.mesh import android.os.Parcel import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable @Parcelize diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 555a887e3..52a5152c1 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -38,6 +38,7 @@ import com.geeksville.android.GeeksvilleApplication import com.geeksville.android.Logging import com.geeksville.android.ServiceClient import com.geeksville.concurrent.handledLaunch +import com.geeksville.mesh.databinding.ActivityMainBinding import com.geeksville.mesh.model.Channel import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.service.* @@ -54,7 +55,6 @@ 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.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -124,6 +124,8 @@ class MainActivity : AppCompatActivity(), Logging, 13 // seems to be hardwired in CompanionDeviceManager to add 65536 } + private lateinit var binding: ActivityMainBinding + // Used to schedule a coroutine in the GUI thread private val mainScope = CoroutineScope(Dispatchers.Main + Job()) @@ -366,6 +368,8 @@ class MainActivity : AppCompatActivity(), Logging, override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + val prefs = UIViewModel.getPreferences(this) model.ownerName.value = prefs.getString("owner", "")!! @@ -396,15 +400,15 @@ class MainActivity : AppCompatActivity(), Logging, /* setContent { MeshApp() } */ - setContentView(R.layout.activity_main) + setContentView(binding.root) initToolbar() - pager.adapter = tabsAdapter - pager.isUserInputEnabled = + binding.pager.adapter = tabsAdapter + binding.pager.isUserInputEnabled = false // Gestures for screen switching doesn't work so good with the map view // pager.offscreenPageLimit = 0 // Don't keep any offscreen pages around, because we want to make sure our bluetooth scanning stops - TabLayoutMediator(tab_layout, pager) { tab, position -> + TabLayoutMediator(binding.tabLayout, binding.pager) { tab, position -> // tab.text = tabInfos[position].text // I think it looks better with icons only tab.icon = getDrawable(tabInfos[position].icon) }.attach() @@ -893,7 +897,7 @@ class MainActivity : AppCompatActivity(), Logging, } private fun showSettingsPage() { - pager.currentItem = 5 + binding.pager.currentItem = 5 } override fun onCreateOptionsMenu(menu: Menu): Boolean { diff --git a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt index f0a81085a..77d26af77 100644 --- a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt @@ -4,7 +4,7 @@ import android.os.Parcelable import com.geeksville.mesh.ui.bearing import com.geeksville.mesh.ui.latLongToMeter import com.geeksville.util.anonymize -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable 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 ef983d06d..c7d03f68f 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -591,7 +591,7 @@ class MeshService : Service(), Logging { if (myInfo.myNodeNum == packet.from) debug("Ignoring retransmission of our packet ${bytes.size}") else { - debug("Received data from $fromId ${bytes.size}") + debug("Received data from $fromId, portnum=${data.portnumValue} ${bytes.size} bytes") dataPacket.status = MessageStatus.RECEIVED rememberDataPacket(dataPacket) @@ -616,10 +616,7 @@ class MeshService : Service(), Logging { val u = MeshProtos.User.parseFrom(data.payload) handleReceivedUser(packet.from, u) } - - else -> { - debug("Received other data packet") - }} + } // We always tell other apps when new data packets arrive serviceBroadcasts.broadcastReceivedData(dataPacket) @@ -1466,7 +1463,7 @@ class MeshService : Service(), Logging { // Keep a record of datapackets, so GUIs can show proper chat history rememberDataPacket(p) - if(p.bytes.size >= MeshProtos.Constants.DATA_PAYLOAD_LEN.number) { + if (p.bytes.size >= MeshProtos.Constants.DATA_PAYLOAD_LEN.number) { p.status = MessageStatus.ERROR throw RemoteException("Message too long") } 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 fb3b113b9..ecc3cfe76 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -18,6 +18,7 @@ import com.geeksville.android.Logging import com.geeksville.android.hideKeyboard import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.R +import com.geeksville.mesh.databinding.ChannelFragmentBinding import com.geeksville.mesh.model.Channel import com.geeksville.mesh.model.ChannelOption import com.geeksville.mesh.model.UIViewModel @@ -25,7 +26,6 @@ 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 -import kotlinx.android.synthetic.main.channel_fragment.* import java.security.SecureRandom @@ -46,26 +46,31 @@ fun ImageView.setOpaque() { class ChannelFragment : ScreenFragment("Channel"), Logging { + private var _binding: ChannelFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. + private val binding get() = _binding!! + private val model: UIViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - return inflater.inflate(R.layout.channel_fragment, container, false) + _binding = ChannelFragmentBinding.inflate(inflater, container, false) + return binding.root } /// Called when the lock/unlock icon has changed private fun onEditingChanged() { - val isEditing = editableCheckbox.isChecked + val isEditing = binding.editableCheckbox.isChecked - channelOptions.isEnabled = isEditing - shareButton.isEnabled = !isEditing - channelNameView.isEnabled = isEditing + binding.channelOptions.isEnabled = isEditing + binding.shareButton.isEnabled = !isEditing + binding.channelNameView.isEnabled = isEditing if (isEditing) // Dim the (stale) QR code while editing... - qrView.setDim() + binding.qrView.setDim() else - qrView.setOpaque() + binding.qrView.setOpaque() } /// Pull the latest data from the model (discarding any user edits) @@ -73,31 +78,31 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { val radioConfig = model.radioConfig.value val channel = UIViewModel.getChannel(radioConfig) - editableCheckbox.isChecked = false // start locked + binding.editableCheckbox.isChecked = false // start locked if (channel != null) { - qrView.visibility = View.VISIBLE - channelNameEdit.visibility = View.VISIBLE - channelNameEdit.setText(channel.humanName) + binding.qrView.visibility = View.VISIBLE + binding.channelNameEdit.visibility = View.VISIBLE + binding.channelNameEdit.setText(channel.humanName) // For now, we only let the user edit/save channels while the radio is awake - because the service // doesn't cache radioconfig writes. val connected = model.isConnected.value == MeshService.ConnectionState.CONNECTED - editableCheckbox.isEnabled = connected + binding.editableCheckbox.isEnabled = connected - qrView.setImageBitmap(channel.getChannelQR()) + binding.qrView.setImageBitmap(channel.getChannelQR()) val modemConfig = radioConfig?.channelSettings?.modemConfig val channelOption = ChannelOption.fromConfig(modemConfig) - filled_exposed_dropdown.setText( + binding.filledExposedDropdown.setText( getString( channelOption?.configRes ?: R.string.modem_config_unrecognized ), false ) } else { - qrView.visibility = View.INVISIBLE - channelNameEdit.visibility = View.INVISIBLE - editableCheckbox.isEnabled = false + binding.qrView.visibility = View.INVISIBLE + binding.channelNameEdit.visibility = View.INVISIBLE + binding.editableCheckbox.isEnabled = false } onEditingChanged() // we just locked the gui @@ -109,7 +114,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { modemConfigList ) - filled_exposed_dropdown.setAdapter(adapter) + binding.filledExposedDropdown.setAdapter(adapter) } private fun shareChannel() { @@ -138,17 +143,17 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - channelNameEdit.on(EditorInfo.IME_ACTION_DONE) { + binding.channelNameEdit.on(EditorInfo.IME_ACTION_DONE) { requireActivity().hideKeyboard() } // Note: Do not use setOnCheckedChanged here because we don't want to be called when we programmatically disable editing - editableCheckbox.setOnClickListener { _ -> - val checked = editableCheckbox.isChecked + binding.editableCheckbox.setOnClickListener { _ -> + val checked = binding.editableCheckbox.isChecked if (checked) { // User just unlocked for editing - remove the # goo around the channel name UIViewModel.getChannel(model.radioConfig.value)?.let { channel -> - channelNameEdit.setText(channel.name) + binding.channelNameEdit.setText(channel.name) } } else { // User just locked it, we should warn and then apply changes to radio @@ -162,7 +167,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { // Generate a new channel with only the changes the user can change in the GUI UIViewModel.getChannel(model.radioConfig.value)?.let { old -> val newSettings = old.settings.toBuilder() - newSettings.name = channelNameEdit.text.toString().trim() + newSettings.name = binding.channelNameEdit.text.toString().trim() // Generate a new AES256 key (for any channel not named Default) if (!newSettings.name.equals( @@ -183,7 +188,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { } val selectedChannelOptionString = - filled_exposed_dropdown.editableText.toString() + binding.filledExposedDropdown.editableText.toString() val modemConfig = getModemConfig(selectedChannelOptionString) if (modemConfig != MeshProtos.ChannelSettings.ModemConfig.UNRECOGNIZED) @@ -199,7 +204,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { // Tell the user to try again Snackbar.make( - editableCheckbox, + binding.editableCheckbox, R.string.radio_sleeping, Snackbar.LENGTH_SHORT ).show() @@ -213,7 +218,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { } // Share this particular channel if someone clicks share - shareButton.setOnClickListener { + binding.shareButton.setOnClickListener { shareChannel() } diff --git a/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt index 6257f6b5c..75822cdb2 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt @@ -1,50 +1,54 @@ -package com.geeksville.mesh.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.Observer -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.geeksville.mesh.R -import com.geeksville.mesh.model.UIViewModel -import kotlinx.android.synthetic.main.debug_fragment.* - -class DebugFragment : Fragment() { - - val model: UIViewModel by viewModels() - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - - return inflater.inflate(R.layout.debug_fragment, container, false) - } - //Button to clear All log - - //List all log - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val recyclerView = view.findViewById(R.id.packets_recyclerview) - val adapter = PacketListAdapter(requireContext()) - - recyclerView.adapter = adapter - recyclerView.layoutManager = LinearLayoutManager(requireContext()) - - clearButton.setOnClickListener { - model.deleteAllPacket() - } - - closeButton.setOnClickListener{ - parentFragmentManager.popBackStack(); - } - model.allPackets.observe(viewLifecycleOwner, Observer { - packets -> packets?.let { adapter.setPackets(it) } - }) - } +package com.geeksville.mesh.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.geeksville.mesh.R +import com.geeksville.mesh.databinding.DebugFragmentBinding +import com.geeksville.mesh.model.UIViewModel + +class DebugFragment : Fragment() { + + private var _binding: DebugFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. + private val binding get() = _binding!! + + val model: UIViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = DebugFragmentBinding.inflate(inflater, container, false) + return binding.root + } + //Button to clear All log + + //List all log + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val recyclerView = view.findViewById(R.id.packets_recyclerview) + val adapter = PacketListAdapter(requireContext()) + + recyclerView.adapter = adapter + recyclerView.layoutManager = LinearLayoutManager(requireContext()) + + binding.clearButton.setOnClickListener { + model.deleteAllPacket() + } + + binding.closeButton.setOnClickListener{ + parentFragmentManager.popBackStack(); + } + model.allPackets.observe(viewLifecycleOwner, Observer { + packets -> packets?.let { adapter.setPackets(it) } + }) + } } \ No newline at end of file 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 2093dfcc8..dbec8b65e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -16,11 +16,11 @@ import com.geeksville.android.Logging import com.geeksville.mesh.DataPacket import com.geeksville.mesh.MessageStatus import com.geeksville.mesh.R +import com.geeksville.mesh.databinding.AdapterMessageLayoutBinding +import com.geeksville.mesh.databinding.MessagesFragmentBinding import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.service.MeshService import com.google.android.material.chip.Chip -import kotlinx.android.synthetic.main.adapter_message_layout.view.* -import kotlinx.android.synthetic.main.messages_fragment.* import java.text.DateFormat import java.util.* @@ -38,13 +38,17 @@ fun EditText.on(actionId: Int, func: () -> Unit) { class MessagesFragment : ScreenFragment("Messages"), Logging { + private var _binding: MessagesFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. + private val binding get() = _binding!! + private val model: UIViewModel by activityViewModels() private val dateTimeFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM) // Provide a direct reference to each of the views within a data item // Used to cache the views within the item layout for fast access - class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + class ViewHolder(itemView: AdapterMessageLayoutBinding) : RecyclerView.ViewHolder(itemView.root) { val username: Chip = itemView.username val messageText: TextView = itemView.messageText val messageTime: TextView = itemView.messageTime @@ -82,10 +86,10 @@ class MessagesFragment : ScreenFragment("Messages"), Logging { // Inflate the custom layout // Inflate the custom layout - val contactView: View = inflater.inflate(R.layout.adapter_message_layout, parent, false) + val contactViewBinding = AdapterMessageLayoutBinding.inflate(inflater, parent, false) // Return a new holder instance - return ViewHolder(contactView) + return ViewHolder(contactViewBinding) } /** @@ -159,7 +163,7 @@ class MessagesFragment : ScreenFragment("Messages"), Logging { // scroll to the last line if (itemCount != 0) - messageListView.scrollToPosition(itemCount - 1) + binding.messageListView.scrollToPosition(itemCount - 1) } } @@ -167,27 +171,28 @@ class MessagesFragment : ScreenFragment("Messages"), Logging { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - return inflater.inflate(R.layout.messages_fragment, container, false) + _binding = MessagesFragmentBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - messageInputText.on(EditorInfo.IME_ACTION_DONE) { + binding.messageInputText.on(EditorInfo.IME_ACTION_DONE) { debug("did IME action") - val str = messageInputText.text.toString().trim() + val str = binding.messageInputText.text.toString().trim() if (str.isNotEmpty()) model.messagesState.sendMessage(str) - messageInputText.setText("") // blow away the string the user just entered + binding.messageInputText.setText("") // blow away the string the user just entered // requireActivity().hideKeyboard() } - messageListView.adapter = messagesAdapter + binding.messageListView.adapter = messagesAdapter val layoutManager = LinearLayoutManager(requireContext()) layoutManager.stackFromEnd = true // We want the last rows to always be shown - messageListView.layoutManager = layoutManager + binding.messageListView.layoutManager = layoutManager model.messagesState.messages.observe(viewLifecycleOwner, Observer { debug("New messages received: ${it.size}") @@ -197,13 +202,13 @@ class MessagesFragment : ScreenFragment("Messages"), Logging { // If connection state _OR_ myID changes we have to fix our ability to edit outgoing messages model.isConnected.observe(viewLifecycleOwner, Observer { connected -> // If we don't know our node ID and we are offline don't let user try to send - textInputLayout.isEnabled = + binding.textInputLayout.isEnabled = connected != MeshService.ConnectionState.DISCONNECTED && model.nodeDB.myId.value != null }) model.nodeDB.myId.observe(viewLifecycleOwner, Observer { myId -> // If we don't know our node ID and we are offline don't let user try to send - textInputLayout.isEnabled = + binding.textInputLayout.isEnabled = model.isConnected.value != MeshService.ConnectionState.DISCONNECTED && myId != null }) } 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 c7c401db6..889792f5b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -34,6 +34,7 @@ import com.geeksville.mesh.MainActivity import com.geeksville.mesh.R import com.geeksville.mesh.android.bluetoothManager import com.geeksville.mesh.android.usbManager +import com.geeksville.mesh.databinding.SettingsFragmentBinding import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.service.BluetoothInterface import com.geeksville.mesh.service.MeshService @@ -46,7 +47,6 @@ import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationSettingsRequest import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.hoho.android.usbserial.driver.UsbSerialDriver -import kotlinx.android.synthetic.main.settings_fragment.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -452,6 +452,10 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { @SuppressLint("NewApi") class SettingsFragment : ScreenFragment("Settings"), Logging { + private var _binding: SettingsFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. + private val binding get() = _binding!! + private val scanModel: BTScanModel by activityViewModels() private val model: UIViewModel by activityViewModels() @@ -478,23 +482,23 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { mainScope.handledLaunch { debug("User started firmware update") - updateFirmwareButton.isEnabled = false // Disable until things complete - updateProgressBar.visibility = View.VISIBLE - updateProgressBar.progress = 0 // start from scratch + binding.updateFirmwareButton.isEnabled = false // Disable until things complete + binding.updateProgressBar.visibility = View.VISIBLE + binding.updateProgressBar.progress = 0 // start from scratch - scanStatusText.text = "Updating firmware, wait up to eight minutes..." + binding.scanStatusText.text = "Updating firmware, wait up to eight minutes..." try { service.startFirmwareUpdate() while (service.updateStatus >= 0) { - updateProgressBar.progress = service.updateStatus + binding.updateProgressBar.progress = service.updateStatus delay(2000) // Only check occasionally } } finally { val isSuccess = (service.updateStatus == -1) - scanStatusText.text = + binding.scanStatusText.text = if (isSuccess) "Update successful" else "Update failed" - updateProgressBar.isEnabled = false - updateFirmwareButton.isEnabled = !isSuccess + binding.updateProgressBar.isEnabled = false + binding.updateFirmwareButton.isEnabled = !isSuccess } } } @@ -504,7 +508,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - return inflater.inflate(R.layout.settings_fragment, container, false) + _binding = SettingsFragmentBinding.inflate(inflater, container, false) + return binding.root } private fun initNodeInfo() { @@ -513,36 +518,36 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { // If actively connected possibly let the user update firmware val info = model.myNodeInfo.value if (connected == MeshService.ConnectionState.CONNECTED && info != null && info.shouldUpdate && info.couldUpdate) { - updateFirmwareButton.visibility = View.VISIBLE - updateFirmwareButton.isEnabled = true - updateFirmwareButton.text = + binding.updateFirmwareButton.visibility = View.VISIBLE + binding.updateFirmwareButton.isEnabled = true + binding.updateFirmwareButton.text = getString(R.string.update_to).format(getString(R.string.cur_firmware_version)) } else { - updateFirmwareButton.visibility = View.GONE - updateProgressBar.visibility = View.GONE + binding.updateFirmwareButton.visibility = View.GONE + binding.updateProgressBar.visibility = View.GONE } when (connected) { MeshService.ConnectionState.CONNECTED -> { val fwStr = info?.firmwareString ?: "" - scanStatusText.text = getString(R.string.connected_to).format(fwStr) + binding.scanStatusText.text = getString(R.string.connected_to).format(fwStr) } MeshService.ConnectionState.DISCONNECTED -> - scanStatusText.text = getString(R.string.not_connected) + binding.scanStatusText.text = getString(R.string.not_connected) MeshService.ConnectionState.DEVICE_SLEEP -> - scanStatusText.text = getString(R.string.connected_sleeping) + binding.scanStatusText.text = getString(R.string.connected_sleeping) } } /// Setup the ui widgets unrelated to BLE scanning private fun initCommonUI() { model.ownerName.observe(viewLifecycleOwner, Observer { name -> - usernameEditText.setText(name) + binding.usernameEditText.setText(name) }) // Only let user edit their name or set software update while connected to a radio model.isConnected.observe(viewLifecycleOwner, Observer { connected -> - usernameView.isEnabled = connected == MeshService.ConnectionState.CONNECTED + binding.usernameView.isEnabled = connected == MeshService.ConnectionState.CONNECTED if (connected == MeshService.ConnectionState.DISCONNECTED) model.ownerName.value = "" initNodeInfo() @@ -553,13 +558,13 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { initNodeInfo() }) - updateFirmwareButton.setOnClickListener { + binding.updateFirmwareButton.setOnClickListener { doFirmwareUpdate() } - usernameEditText.on(EditorInfo.IME_ACTION_DONE) { + binding.usernameEditText.on(EditorInfo.IME_ACTION_DONE) { debug("did IME action") - val n = usernameEditText.text.toString().trim() + val n = binding.usernameEditText.text.toString().trim() if (n.isNotEmpty()) model.setOwner(n) @@ -569,17 +574,17 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { val app = (requireContext().applicationContext as GeeksvilleApplication) // Set analytics checkbox - analyticsOkayCheckbox.isChecked = app.isAnalyticsAllowed + binding.analyticsOkayCheckbox.isChecked = app.isAnalyticsAllowed - analyticsOkayCheckbox.setOnCheckedChangeListener { _, isChecked -> + binding.analyticsOkayCheckbox.setOnCheckedChangeListener { _, isChecked -> debug("User changed analytics to $isChecked") app.isAnalyticsAllowed = isChecked - reportBugButton.isEnabled = app.isAnalyticsAllowed + binding.reportBugButton.isEnabled = app.isAnalyticsAllowed } // report bug button only enabled if analytics is allowed - reportBugButton.isEnabled = app.isAnalyticsAllowed - reportBugButton.setOnClickListener { + binding.reportBugButton.isEnabled = app.isAnalyticsAllowed + binding.reportBugButton.setOnClickListener { MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.report_a_bug) .setMessage(getString(R.string.report_bug_text)) @@ -600,34 +605,34 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { b.isEnabled = enabled b.isChecked = device.address == scanModel.selectedNotNull && device.bonded // Only show checkbox if device is still paired - deviceRadioGroup.addView(b) + binding.deviceRadioGroup.addView(b) // Once we have at least one device, don't show the "looking for" animation - it makes uers think // something is busted - scanProgressBar.visibility = View.INVISIBLE + binding.scanProgressBar.visibility = View.INVISIBLE b.setOnClickListener { if (!device.bonded) // If user just clicked on us, try to bond - scanStatusText.setText(R.string.starting_pairing) + binding.scanStatusText.setText(R.string.starting_pairing) b.isChecked = scanModel.onSelected(requireActivity() as MainActivity, device) if (!b.isSelected) - scanStatusText.setText(getString(R.string.please_pair)) + binding.scanStatusText.setText(getString(R.string.please_pair)) } } /// Show the GUI for classic scanning private fun showClassicWidgets(visible: Int) { - scanProgressBar.visibility = visible - deviceRadioGroup.visibility = visible + binding.scanProgressBar.visibility = visible + binding.deviceRadioGroup.visibility = visible } /// Setup the GUI to do a classic (pre SDK 26 BLE scan) private fun initClassicScan() { // Turn off the widgets for the new API (we turn on/off hte classic widgets when we start scanning - changeRadioButton.visibility = View.GONE + binding.changeRadioButton.visibility = View.GONE showClassicWidgets(View.VISIBLE) @@ -640,13 +645,13 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { scanModel.errorText.observe(viewLifecycleOwner, Observer { errMsg -> if (errMsg != null) { - scanStatusText.text = errMsg + binding.scanStatusText.text = errMsg } }) scanModel.devices.observe(viewLifecycleOwner, Observer { devices -> // Remove the old radio buttons and repopulate - deviceRadioGroup.removeAllViews() + binding.deviceRadioGroup.removeAllViews() val adapter = scanModel.bluetoothAdapter @@ -690,14 +695,14 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { RadioInterfaceService.getBondedDeviceAddress(requireContext()) != null // get rid of the warning text once at least one device is paired - warningNotPaired.visibility = if (hasBonded) View.GONE else View.VISIBLE + binding.warningNotPaired.visibility = if (hasBonded) View.GONE else View.VISIBLE }) } /// Start running the modern scan, once it has one result we enable the private fun startBackgroundScan() { // Disable the change button until our scan has some results - changeRadioButton.isEnabled = false + binding.changeRadioButton.isEnabled = false // To skip filtering based on name and supported feature flags (UUIDs), // don't include calls to setNamePattern() and addServiceUuid(), @@ -726,8 +731,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { override fun onDeviceFound(chooserLauncher: IntentSender) { debug("Found one device - enabling button") - changeRadioButton.isEnabled = true - changeRadioButton.setOnClickListener { + binding.changeRadioButton.isEnabled = true + binding.changeRadioButton.setOnClickListener { debug("User clicked BLE change button") // Request code seems to be ignored anyways @@ -748,18 +753,18 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { private fun initModernScan() { // Turn off the widgets for the classic API - scanProgressBar.visibility = View.GONE - deviceRadioGroup.visibility = View.GONE - changeRadioButton.visibility = View.VISIBLE + binding.scanProgressBar.visibility = View.GONE + binding.deviceRadioGroup.visibility = View.GONE + binding.changeRadioButton.visibility = View.VISIBLE val curRadio = RadioInterfaceService.getBondedDeviceAddress(requireContext()) if (curRadio != null) { - scanStatusText.text = getString(R.string.current_pair).format(curRadio) - changeRadioButton.text = getString(R.string.change_radio) + binding.scanStatusText.text = getString(R.string.current_pair).format(curRadio) + binding.changeRadioButton.text = getString(R.string.change_radio) } else { - scanStatusText.text = getString(R.string.not_paired_yet) - changeRadioButton.setText(R.string.select_radio) + binding.scanStatusText.text = getString(R.string.not_paired_yet) + binding.changeRadioButton.setText(R.string.select_radio) } startBackgroundScan() diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index c3bad63f0..0b6529679 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -14,22 +14,26 @@ import androidx.recyclerview.widget.RecyclerView import com.geeksville.android.Logging import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.R +import com.geeksville.mesh.databinding.AdapterNodeLayoutBinding +import com.geeksville.mesh.databinding.NodelistFragmentBinding import com.geeksville.mesh.model.UIViewModel -import kotlinx.android.synthetic.main.adapter_node_layout.view.* -import kotlinx.android.synthetic.main.nodelist_fragment.* import java.text.ParseException import java.util.* class UsersFragment : ScreenFragment("Users"), Logging { + private var _binding: NodelistFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. + private val binding get() = _binding!! + private val model: UIViewModel by activityViewModels() // Provide a direct reference to each of the views within a data item // Used to cache the views within the item layout for fast access - class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + class ViewHolder(itemView: AdapterNodeLayoutBinding) : RecyclerView.ViewHolder(itemView.root) { val nodeNameView = itemView.nodeNameView - val distance_view = itemView.distance_view + val distanceView = itemView.distanceView val batteryPctView = itemView.batteryPercentageView val lastTime = itemView.lastConnectionView val powerIcon = itemView.batteryIcon @@ -64,9 +68,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { val inflater = LayoutInflater.from(requireContext()) // Inflate the custom layout - - // Inflate the custom layout - val contactView: View = inflater.inflate(R.layout.adapter_node_layout, parent, false) + val contactView = AdapterNodeLayoutBinding.inflate(inflater, parent, false) // Return a new holder instance return ViewHolder(contactView) @@ -108,10 +110,10 @@ class UsersFragment : ScreenFragment("Users"), Logging { val ourNodeInfo = model.nodeDB.ourNodeInfo val distance = ourNodeInfo?.distanceStr(n) if (distance != null) { - holder.distance_view.text = distance - holder.distance_view.visibility = View.VISIBLE + holder.distanceView.text = distance + holder.distanceView.visibility = View.VISIBLE } else { - holder.distance_view.visibility = View.INVISIBLE + holder.distanceView.visibility = View.INVISIBLE } renderBattery(n.batteryPctLevel, holder) @@ -176,14 +178,15 @@ class UsersFragment : ScreenFragment("Users"), Logging { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - return inflater.inflate(R.layout.nodelist_fragment, container, false) + _binding = NodelistFragmentBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - nodeListView.adapter = nodesAdapter - nodeListView.layoutManager = LinearLayoutManager(requireContext()) + binding.nodeListView.adapter = nodesAdapter + binding.nodeListView.layoutManager = LinearLayoutManager(requireContext()) model.nodeDB.nodes.observe(viewLifecycleOwner, Observer { it -> nodesAdapter.onNodesChanged(it.values) From c4439eb34cadbf330e6dc5565a0a9a288039ba27 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Thu, 10 Dec 2020 09:23:02 +0800 Subject: [PATCH 9/9] WIP use new position api --- .../geeksville/mesh/service/DeviceVersion.kt | 33 +++++++++++++++++++ .../geeksville/mesh/service/MeshService.kt | 33 ++++++++++++++----- .../mesh/service/SoftwareUpdateService.kt | 31 ++++------------- 3 files changed, 65 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/service/DeviceVersion.kt diff --git a/app/src/main/java/com/geeksville/mesh/service/DeviceVersion.kt b/app/src/main/java/com/geeksville/mesh/service/DeviceVersion.kt new file mode 100644 index 000000000..13ef461e0 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/service/DeviceVersion.kt @@ -0,0 +1,33 @@ +package com.geeksville.mesh.service + +import com.geeksville.android.Logging + +/** + * Provide structured access to parse and compare device version strings + */ +data class DeviceVersion(val asString: String): Comparable, Logging { + + val asInt get() = try { + verStringToInt(asString) + } catch(e: Exception) { + warn("Exception while parsing version '$asString', assuming version 0") + 0 + } + + /** + * Convert a version string of the form 1.23.57 to a comparable integer of + * the form 12357. + * + * Or throw an exception if the string can not be parsed + */ + private fun verStringToInt(s: String): Int { + // Allow 1 to two digits per match + val match = + Regex("(\\d{1,2}).(\\d{1,2}).(\\d{1,2})").find(s) + ?: throw Exception("Can't parse version $s") + val (major, minor, build) = match.destructured + return major.toInt() * 1000 + minor.toInt() * 100 + build.toInt() + } + + override fun compareTo(other: DeviceVersion): Int = asInt - other.asInt +} \ No newline at end of file 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 c7d03f68f..c294e8ac3 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -91,6 +91,9 @@ class MeshService : Service(), Logging { private var packetRepo: PacketRepository? = null private var fusedLocationClient: FusedLocationProviderClient? = null + // If we've ever read a valid region code from our device it will be here + var curRegionValue = MeshProtos.RegionCode.Unset_VALUE + val radio = ServiceClient { IRadioInterfaceService.Stub.asInterface(it).apply { // Now that we are connected to the radio service, tell it to connect to the radio @@ -418,6 +421,8 @@ class MeshService : Service(), Logging { /// END OF MODEL /// + val deviceVersion get() = DeviceVersion(myNodeInfo?.firmwareVersion ?: "") + /// Map a nodenum to a node, or throw an exception if not found private fun toNodeInfo(n: Int) = nodeDBbyNodeNum[n] ?: throw NodeNumNotFoundException( n @@ -555,12 +560,15 @@ class MeshService : Service(), Logging { } } + /// Syntactic sugar to create data subpackets + private fun makeData(portNum: Int, bytes: ByteString) = MeshProtos.Data.newBuilder().also { + it.portnumValue = portNum + it.payload = bytes + }.build() + private fun toMeshPacket(p: DataPacket): MeshPacket { return buildMeshPacket(p.to!!, id = p.id, wantAck = true) { - data = MeshProtos.Data.newBuilder().also { - it.portnumValue = p.dataType - it.payload = ByteString.copyFrom(p.bytes) - }.build() + data = makeData(p.dataType, ByteString.copyFrom(p.bytes)) } } @@ -1066,7 +1074,10 @@ class MeshService : Service(), Logging { hwModel, firmwareVersion, firmwareUpdateFilename != null, - SoftwareUpdateService.shouldUpdate(this@MeshService, firmwareVersion), + SoftwareUpdateService.shouldUpdate( + this@MeshService, + DeviceVersion(firmwareVersion) + ), currentPacketId.toLong() and 0xffffffffL, if (nodeNumBits == 0) 8 else nodeNumBits, if (packetIdBits == 0) 8 else packetIdBits, @@ -1100,8 +1111,7 @@ class MeshService : Service(), Logging { } } - // If we've ever read a valid region code from our device it will be here - var curRegionValue = MeshProtos.RegionCode.Unset_VALUE + /** * If we are updating nodes we might need to use old (fixed by firmware build) @@ -1227,7 +1237,14 @@ class MeshService : Service(), Logging { val packet = newMeshPacketTo(destNum) packet.decoded = MeshProtos.SubPacket.newBuilder().also { - it.position = position + val isNewPositionAPI = deviceVersion >= DeviceVersion("1.20.0") // We changed position APIs with this version + if (isNewPositionAPI) { + // Use the new position as data format + it.data = makeData(Portnums.PortNum.POSITION_APP_VALUE, position.toByteString()) + } else { + // Send the old dedicated position subpacket + it.position = position + } it.wantResponse = wantResponse }.build() 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 5a4fe4b39..69d57639c 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt @@ -186,20 +186,7 @@ class SoftwareUpdateService : JobIntentService(), Logging { ) } - /** - * Convert a version string of the form 1.23.57 to a comparable integer of - * the form 12357. - * - * Or throw an exception if the string can not be parsed - */ - fun verStringToInt(s: String): Int { - // Allow 1 to two digits per match - val match = - Regex("(\\d{1,2}).(\\d{1,2}).(\\d{1,2})").find(s) - ?: throw Exception("Can't parse version $s") - val (major, minor, build) = match.destructured - return major.toInt() * 1000 + minor.toInt() * 100 + build.toInt() - } + /** Return true if we thing the firmwarte shoulde be updated * @@ -207,15 +194,11 @@ class SoftwareUpdateService : JobIntentService(), Logging { */ fun shouldUpdate( context: Context, - swVer: String + deviceVersion: DeviceVersion ): Boolean = try { - val curVer = verStringToInt(context.getString(R.string.cur_firmware_version)) + val curVer = DeviceVersion(context.getString(R.string.cur_firmware_version)) val minVer = - verStringToInt("0.7.8") // The oldest device version with a working software update service - - // If the user is running a development build we never do an automatic update - val deviceVersion = - verStringToInt(if (swVer.isEmpty() || swVer == "unset") "99.99.99" else swVer) + DeviceVersion("0.7.8") // The oldest device version with a working software update service (curVer > deviceVersion) && (deviceVersion >= minVer) } catch (ex: Exception) { @@ -229,13 +212,13 @@ class SoftwareUpdateService : JobIntentService(), Logging { context: Context, mfg: String ): UpdateFilenames { - val curver = context.getString(R.string.cur_firmware_version) + val curVer = context.getString(R.string.cur_firmware_version) // Check to see if the file exists (some builds might not include update files for size reasons) val firmwareFiles = context.assets.list("firmware") ?: arrayOf() - val appLoad = "firmware-$mfg-$curver.bin" - val spiffs = "spiffs-$curver.bin" + val appLoad = "firmware-$mfg-$curVer.bin" + val spiffs = "spiffs-$curVer.bin" return UpdateFilenames( if (firmwareFiles.contains(appLoad))