sforkowany z mirror/meshtastic-android
we now save node & msg db across service invocations
rodzic
4f24794001
commit
39eb6664da
|
@ -1,6 +1,7 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
||||
// Apply the Crashlytics Gradle plugin
|
||||
|
@ -103,6 +104,9 @@ dependencies {
|
|||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
|
||||
// kotlin serialization
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0"
|
||||
|
||||
// rate this app
|
||||
implementation "com.vorlonsoft:androidrate:1.2.1"
|
||||
|
||||
|
|
|
@ -28,5 +28,16 @@
|
|||
# Needed for protobufs
|
||||
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite { <fields>; }
|
||||
|
||||
# for kotlinx.serialization
|
||||
-keepattributes *Annotation*, InnerClasses
|
||||
-dontnote kotlinx.serialization.SerializationKt
|
||||
-keep,includedescriptorclasses class com.yourcompany.yourpackage.**$$serializer { *; } # <-- change package name to your app's
|
||||
-keepclassmembers class com.geeksville.mesh.** { # <-- change package name to your app's
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class com.geeksville.mesh.** { # <-- change package name to your app's
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# Our app is opensource no need to obsfucate
|
||||
-dontobfuscate
|
|
@ -4,6 +4,7 @@ import android.os.Parcelable
|
|||
import com.geeksville.mesh.ui.bearing
|
||||
import com.geeksville.mesh.ui.latLongToMeter
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
/**
|
||||
|
@ -23,6 +24,7 @@ val Any?.anonymized: String
|
|||
/**
|
||||
* A parcelable version of the protobuf MeshPacket + Data subpacket.
|
||||
*/
|
||||
@Serializable
|
||||
@Parcelize
|
||||
data class DataPacket(
|
||||
val from: String, // a nodeID string
|
||||
|
@ -61,6 +63,7 @@ data class DataPacket(
|
|||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@Parcelize
|
||||
data class MeshUser(val id: String, val longName: String, val shortName: String) :
|
||||
Parcelable {
|
||||
|
@ -70,6 +73,7 @@ data class MeshUser(val id: String, val longName: String, val shortName: String)
|
|||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@Parcelize
|
||||
data class Position(
|
||||
val latitude: Double,
|
||||
|
@ -89,6 +93,7 @@ data class Position(
|
|||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
@Parcelize
|
||||
data class NodeInfo(
|
||||
val num: Int, // This is immutable, and used as a key
|
||||
|
|
|
@ -14,6 +14,7 @@ import androidx.annotation.RequiresApi
|
|||
import androidx.annotation.UiThread
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat.PRIORITY_MIN
|
||||
import androidx.core.content.edit
|
||||
import com.geeksville.analytics.DataPair
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
|
@ -29,6 +30,9 @@ import com.google.android.gms.common.api.ResolvableApiException
|
|||
import com.google.android.gms.location.*
|
||||
import com.google.protobuf.ByteString
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
|
@ -100,7 +104,7 @@ class MeshService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
public enum class ConnectionState {
|
||||
enum class ConnectionState {
|
||||
DISCONNECTED,
|
||||
CONNECTED,
|
||||
DEVICE_SLEEP // device is in LS sleep state, it will reconnected to us over bluetooth once it has data
|
||||
|
@ -379,18 +383,23 @@ class MeshService : Service(), Logging {
|
|||
info("Creating mesh service")
|
||||
startForeground()
|
||||
|
||||
// we listen for messages from the radio receiver _before_ trying to create the service
|
||||
val filter = IntentFilter()
|
||||
filter.addAction(RadioInterfaceService.RECEIVE_FROMRADIO_ACTION)
|
||||
filter.addAction(RadioInterfaceService.RADIO_CONNECTED_ACTION)
|
||||
registerReceiver(radioInterfaceReceiver, filter)
|
||||
// Switch to the IO thread
|
||||
serviceScope.handledLaunch {
|
||||
loadSettings() // Load our last known node DB
|
||||
|
||||
// We in turn need to use the radiointerface service
|
||||
val intent = Intent(this, RadioInterfaceService::class.java)
|
||||
// intent.action = IMeshService::class.java.name
|
||||
radio.connect(this, intent, Context.BIND_AUTO_CREATE)
|
||||
// we listen for messages from the radio receiver _before_ trying to create the service
|
||||
val filter = IntentFilter()
|
||||
filter.addAction(RadioInterfaceService.RECEIVE_FROMRADIO_ACTION)
|
||||
filter.addAction(RadioInterfaceService.RADIO_CONNECTED_ACTION)
|
||||
registerReceiver(radioInterfaceReceiver, filter)
|
||||
|
||||
// the rest of our init will happen once we are in radioConnection.onServiceConnected
|
||||
// We in turn need to use the radiointerface service
|
||||
val intent = Intent(this@MeshService, RadioInterfaceService::class.java)
|
||||
// intent.action = IMeshService::class.java.name
|
||||
radio.connect(this@MeshService, intent, Context.BIND_AUTO_CREATE)
|
||||
|
||||
// the rest of our init will happen once we are in radioConnection.onServiceConnected
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -398,6 +407,7 @@ class MeshService : Service(), Logging {
|
|||
info("Destroying mesh service")
|
||||
unregisterReceiver(radioInterfaceReceiver)
|
||||
radio.close()
|
||||
saveSettings()
|
||||
|
||||
super.onDestroy()
|
||||
serviceJob.cancel()
|
||||
|
@ -412,6 +422,7 @@ class MeshService : Service(), Logging {
|
|||
val NODENUM_BROADCAST = 255
|
||||
|
||||
// MyNodeInfo sent via special protobuf from radio
|
||||
@Serializable
|
||||
data class MyNodeInfo(
|
||||
val myNodeNum: Int,
|
||||
val hasGPS: Boolean,
|
||||
|
@ -420,6 +431,84 @@ class MeshService : Service(), Logging {
|
|||
val firmwareVersion: String
|
||||
)
|
||||
|
||||
/// Our saved preferences as stored on disk
|
||||
@Serializable
|
||||
private data class SavedSettings(
|
||||
val nodeDB: Array<NodeInfo>,
|
||||
val myInfo: MyNodeInfo,
|
||||
val messages: Array<DataPacket>
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SavedSettings
|
||||
|
||||
if (!nodeDB.contentEquals(other.nodeDB)) return false
|
||||
if (myInfo != other.myInfo) return false
|
||||
if (!messages.contentEquals(other.messages)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = nodeDB.contentHashCode()
|
||||
result = 31 * result + myInfo.hashCode()
|
||||
result = 31 * result + messages.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPrefs() = getSharedPreferences("service-prefs", Context.MODE_PRIVATE)
|
||||
|
||||
/// Save information about our mesh to disk, so we will have it when we next start the service (even before we hear from our device)
|
||||
private fun saveSettings() {
|
||||
myNodeInfo?.let { myInfo ->
|
||||
val settings = SavedSettings(
|
||||
myInfo = myInfo,
|
||||
nodeDB = nodeDBbyNodeNum.values.toTypedArray(),
|
||||
messages = recentDataPackets.toTypedArray()
|
||||
)
|
||||
val json = Json(JsonConfiguration.Default)
|
||||
val asString = json.stringify(SavedSettings.serializer(), settings)
|
||||
debug("Saving settings as $asString")
|
||||
getPrefs().edit(commit = true) {
|
||||
// FIXME, not really ideal to store this bigish blob in preferences
|
||||
putString("json", asString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load our saved DB state
|
||||
private fun loadSettings() {
|
||||
try {
|
||||
getPrefs().getString("json", null)?.let { asString ->
|
||||
val json = Json(JsonConfiguration.Default)
|
||||
val settings = json.parse(SavedSettings.serializer(), asString)
|
||||
myNodeInfo = settings.myInfo
|
||||
|
||||
// put our node array into our two different map representations
|
||||
nodeDBbyNodeNum.clear()
|
||||
nodeDBbyNodeNum.putAll(settings.nodeDB.map { Pair(it.num, it) })
|
||||
nodeDBbyID.clear()
|
||||
nodeDBbyID.putAll(settings.nodeDB.mapNotNull {
|
||||
it.user?.let { user -> // ignore records that don't have a valid user
|
||||
Pair(
|
||||
user.id,
|
||||
it
|
||||
)
|
||||
}
|
||||
})
|
||||
// Note: we do not haveNodeDB = true because that means we've got a valid db from a real device (rather than this possibly stale hint)
|
||||
|
||||
recentDataPackets.clear()
|
||||
recentDataPackets.addAll(settings.messages)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
errormsg("Ignoring error loading saved state for service: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
var myNodeInfo: MyNodeInfo? = null
|
||||
|
||||
private var radioConfig: MeshProtos.RadioConfig? = null
|
||||
|
@ -819,6 +908,9 @@ class MeshService : Service(), Logging {
|
|||
}
|
||||
|
||||
fun startDisconnect() {
|
||||
// Just in case the user uncleanly reboots the phone, save now (we normally save in onDestroy)
|
||||
saveSettings()
|
||||
|
||||
GeeksvilleApplication.analytics.track(
|
||||
"mesh_disconnect",
|
||||
DataPair("num_nodes", numNodes),
|
||||
|
|
|
@ -12,6 +12,8 @@ buildscript {
|
|||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.0-beta04'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue