we now save node & msg db across service invocations

pull/28/head
geeksville 2020-04-19 17:25:20 -07:00
rodzic 4f24794001
commit 39eb6664da
5 zmienionych plików z 125 dodań i 11 usunięć

Wyświetl plik

@ -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"

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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),

Wyświetl plik

@ -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