diff --git a/app/src/main/java/com/geeksville/mesh/database/Converters.kt b/app/src/main/java/com/geeksville/mesh/database/Converters.kt new file mode 100644 index 000000000..9639bcf3d --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/Converters.kt @@ -0,0 +1,33 @@ +package com.geeksville.mesh.database + +import androidx.room.TypeConverter +import com.geeksville.mesh.DataPacket +import com.geeksville.mesh.MeshProtos.MeshPacket +import com.google.protobuf.TextFormat +import kotlinx.serialization.json.Json + +class Converters { + @TypeConverter + fun dataFromString(value: String): DataPacket { + val json = Json { isLenient = true } + return json.decodeFromString(DataPacket.serializer(), value) + } + + @TypeConverter + fun dataToString(value: DataPacket): String { + val json = Json { isLenient = true } + return json.encodeToString(DataPacket.serializer(), value) + } + + @TypeConverter + fun protoFromString(value: String): MeshPacket { + val builder = MeshPacket.newBuilder() + TextFormat.getParser().merge(value, builder) + return builder.build() + } + + @TypeConverter + fun protoToString(value: MeshPacket): String { + return value.toString() + } +} diff --git a/app/src/main/java/com/geeksville/mesh/database/DatabaseModule.kt b/app/src/main/java/com/geeksville/mesh/database/DatabaseModule.kt index 32c47499f..d7412e61c 100644 --- a/app/src/main/java/com/geeksville/mesh/database/DatabaseModule.kt +++ b/app/src/main/java/com/geeksville/mesh/database/DatabaseModule.kt @@ -2,6 +2,7 @@ package com.geeksville.mesh.database import android.app.Application import com.geeksville.mesh.database.dao.MeshLogDao +import com.geeksville.mesh.database.dao.PacketDao import com.geeksville.mesh.database.dao.QuickChatActionDao import dagger.Module import dagger.Provides @@ -17,6 +18,11 @@ class DatabaseModule { fun provideDatabase(app: Application): MeshtasticDatabase = MeshtasticDatabase.getDatabase(app) + @Provides + fun providePacketDao(database: MeshtasticDatabase): PacketDao { + return database.packetDao() + } + @Provides fun provideMeshLogDao(database: MeshtasticDatabase): MeshLogDao { return database.meshLogDao() diff --git a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt index 8453763cb..8b736f0b9 100644 --- a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt +++ b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt @@ -4,13 +4,18 @@ import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.geeksville.mesh.database.dao.PacketDao import com.geeksville.mesh.database.dao.MeshLogDao import com.geeksville.mesh.database.dao.QuickChatActionDao import com.geeksville.mesh.database.entity.MeshLog +import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.database.entity.QuickChatAction -@Database(entities = [MeshLog::class, QuickChatAction::class], version = 3, exportSchema = false) +@Database(entities = [Packet::class, MeshLog::class, QuickChatAction::class], version = 3, exportSchema = false) +@TypeConverters(Converters::class) abstract class MeshtasticDatabase : RoomDatabase() { + abstract fun packetDao(): PacketDao abstract fun meshLogDao(): MeshLogDao abstract fun quickChatActionDao(): QuickChatActionDao diff --git a/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt b/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt new file mode 100644 index 000000000..af56e3879 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt @@ -0,0 +1,34 @@ +package com.geeksville.mesh.database + +import com.geeksville.mesh.database.dao.PacketDao +import com.geeksville.mesh.database.entity.Packet +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import javax.inject.Inject + +class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Lazy) { + private val packetDao by lazy { + packetDaoLazy.get() + } + + suspend fun getAll(): Flow> = withContext(Dispatchers.IO) { + packetDao.getAllPackets() + } + + suspend fun insert(packet: Packet) = withContext(Dispatchers.IO) { + packetDao.insert(packet) + } + + suspend fun deleteAll() = withContext(Dispatchers.IO) { + packetDao.deleteAll() + } + + suspend fun delete(packet: Packet) = withContext(Dispatchers.IO) { + packetDao.delete(packet) + } + + suspend fun update(packet: Packet) = withContext(Dispatchers.IO) { + packetDao.update(packet) + } +} diff --git a/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt b/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt new file mode 100644 index 000000000..ebb81c4f7 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt @@ -0,0 +1,34 @@ +package com.geeksville.mesh.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Update +import androidx.room.Query +import androidx.room.Transaction +import com.geeksville.mesh.database.entity.Packet +import kotlinx.coroutines.flow.Flow + +@Dao +interface PacketDao { + + @Query("Select * from packet order by received_time asc") + fun getAllPackets(): Flow> + + @Insert + fun insert(packet: Packet) + + @Query("Delete from packet") + fun deleteAll() + + @Query("Delete from packet where uuid=:uuid") + fun _delete(uuid: Long) + + @Transaction + fun delete(packet: Packet) { + _delete(packet.uuid) + } + + @Update + fun update(packet: Packet) + +} diff --git a/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt new file mode 100644 index 000000000..b9d0ed8e4 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt @@ -0,0 +1,19 @@ +package com.geeksville.mesh.database.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.geeksville.mesh.DataPacket +import com.geeksville.mesh.MessageStatus + +@Entity(tableName = "packet") +data class Packet( + @PrimaryKey(autoGenerate = true) val uuid: Long, + @ColumnInfo(name = "port_num") val port_num: Int, + @ColumnInfo(name = "contact_id") val contact_id: String?, + @ColumnInfo(name = "channel") val channel: Int, + @ColumnInfo(name = "status") val status: MessageStatus = MessageStatus.UNKNOWN, + @ColumnInfo(name = "received_time") val received_time: Long, + @ColumnInfo(name = "packet") val packet: DataPacket +) { +} diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 11287b3e5..2d6b279c5 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -16,9 +16,11 @@ import com.geeksville.mesh.* import com.geeksville.mesh.ConfigProtos.Config import com.geeksville.mesh.database.MeshLogRepository import com.geeksville.mesh.database.QuickChatActionRepository +import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.database.entity.MeshLog import com.geeksville.mesh.database.entity.QuickChatAction import com.geeksville.mesh.LocalOnlyProtos.LocalConfig +import com.geeksville.mesh.database.PacketRepository import com.geeksville.mesh.repository.datastore.ChannelSetRepository import com.geeksville.mesh.repository.datastore.LocalConfigRepository import com.geeksville.mesh.service.MeshService @@ -67,6 +69,7 @@ class UIViewModel @Inject constructor( private val app: Application, private val meshLogRepository: MeshLogRepository, private val channelSetRepository: ChannelSetRepository, + private val packetRepository: PacketRepository, private val localConfigRepository: LocalConfigRepository, private val quickChatActionRepository: QuickChatActionRepository, private val preferences: SharedPreferences @@ -75,6 +78,9 @@ class UIViewModel @Inject constructor( private val _meshLog = MutableStateFlow>(emptyList()) val meshLog: StateFlow> = _meshLog + private val _packets = MutableStateFlow>(emptyList()) + val packets: StateFlow> = _packets + private val _localConfig = MutableStateFlow(LocalConfig.getDefaultInstance()) val localConfig: StateFlow = _localConfig val config get() = _localConfig.value @@ -91,6 +97,11 @@ class UIViewModel @Inject constructor( _meshLog.value = logs } } + viewModelScope.launch { + packetRepository.getAll().collect { meshPackets -> + _packets.value = meshPackets + } + } viewModelScope.launch { localConfigRepository.localConfigFlow.collect { config -> _localConfig.value = config 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 fb07b8f1b..bc5c27f14 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -16,7 +16,9 @@ import com.geeksville.mesh.MeshProtos.MeshPacket import com.geeksville.mesh.MeshProtos.ToRadio import com.geeksville.mesh.android.hasBackgroundPermission import com.geeksville.mesh.database.MeshLogRepository +import com.geeksville.mesh.database.PacketRepository import com.geeksville.mesh.database.entity.MeshLog +import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.model.DeviceVersion import com.geeksville.mesh.repository.datastore.ChannelSetRepository import com.geeksville.mesh.repository.datastore.LocalConfigRepository @@ -49,6 +51,9 @@ class MeshService : Service(), Logging { @Inject lateinit var dispatchers: CoroutineDispatchers + @Inject + lateinit var packetRepository: Lazy + @Inject lateinit var meshLogRepository: Lazy @@ -614,7 +619,20 @@ class MeshService : Service(), Logging { private fun rememberDataPacket(dataPacket: DataPacket) { // Now that we use data packets for more things, we need to be choosier about what we keep. Since (currently - in the future less so) // we only care about old text messages, we just store those... - if (dataPacket.dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) { + if (dataPacket.dataType == Portnums.PortNum.WAYPOINT_APP_VALUE + || dataPacket.dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE + ) { + val packetToSave = Packet( + 0L, // autoGenerated + dataPacket.dataType, + if (dataPacket.from == DataPacket.ID_LOCAL || dataPacket.to == DataPacket.ID_BROADCAST) dataPacket.to else dataPacket.from, + dataPacket.channel, + MessageStatus.RECEIVED, + System.currentTimeMillis(), + dataPacket + ) + insertPacket(packetToSave) + // discard old messages if needed then add the new one while (recentDataPackets.size > 100) recentDataPackets.removeAt(0) @@ -930,6 +948,12 @@ class MeshService : Service(), Logging { } } + private fun insertPacket(packet: Packet) { + serviceScope.handledLaunch { + packetRepository.get().insert(packet) + } + } + private fun insertMeshLog(packetToSave: MeshLog) { serviceScope.handledLaunch { // Do not log, because might contain PII