mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: Refactor MeshService into smaller, single-responsibility components (#4108)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
0fa690eb61
commit
b3ebe760dd
35 changed files with 3568 additions and 2816 deletions
|
|
@ -0,0 +1,309 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.service
|
||||
|
||||
import com.geeksville.mesh.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.util.ignoreException
|
||||
import dagger.Lazy
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import org.meshtastic.core.analytics.DataPair
|
||||
import org.meshtastic.core.analytics.platform.PlatformAnalytics
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.database.DatabaseManager
|
||||
import org.meshtastic.core.database.entity.ReactionEntity
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.prefs.mesh.MeshPrefs
|
||||
import org.meshtastic.core.service.MeshServiceNotifications
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.proto.AdminProtos
|
||||
import org.meshtastic.proto.ChannelProtos
|
||||
import org.meshtastic.proto.ConfigProtos
|
||||
import org.meshtastic.proto.MeshProtos
|
||||
import org.meshtastic.proto.ModuleConfigProtos
|
||||
import org.meshtastic.proto.Portnums
|
||||
import org.meshtastic.proto.user
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Suppress("LongParameterList", "TooManyFunctions")
|
||||
@Singleton
|
||||
class MeshActionHandler
|
||||
@Inject
|
||||
constructor(
|
||||
private val nodeManager: MeshNodeManager,
|
||||
private val commandSender: MeshCommandSender,
|
||||
private val packetRepository: Lazy<PacketRepository>,
|
||||
private val serviceBroadcasts: MeshServiceBroadcasts,
|
||||
private val dataHandler: MeshDataHandler,
|
||||
private val analytics: PlatformAnalytics,
|
||||
private val meshPrefs: MeshPrefs,
|
||||
private val databaseManager: DatabaseManager,
|
||||
private val serviceNotifications: MeshServiceNotifications,
|
||||
) {
|
||||
private var scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_REBOOT_DELAY = 5
|
||||
}
|
||||
|
||||
fun onServiceAction(action: ServiceAction) {
|
||||
ignoreException {
|
||||
val myNodeNum = nodeManager.myNodeNum ?: return@ignoreException
|
||||
when (action) {
|
||||
is ServiceAction.Favorite -> {
|
||||
val node = action.node
|
||||
commandSender.sendAdmin(myNodeNum) {
|
||||
if (node.isFavorite) removeFavoriteNode = node.num else setFavoriteNode = node.num
|
||||
}
|
||||
nodeManager.updateNodeInfo(node.num) { it.isFavorite = !node.isFavorite }
|
||||
}
|
||||
is ServiceAction.Ignore -> {
|
||||
val node = action.node
|
||||
commandSender.sendAdmin(myNodeNum) {
|
||||
if (node.isIgnored) removeIgnoredNode = node.num else setIgnoredNode = node.num
|
||||
}
|
||||
nodeManager.updateNodeInfo(node.num) { it.isIgnored = !node.isIgnored }
|
||||
}
|
||||
is ServiceAction.Reaction -> {
|
||||
val channel = action.contactKey[0].digitToInt()
|
||||
val destId = action.contactKey.substring(1)
|
||||
val dataPacket =
|
||||
org.meshtastic.core.model.DataPacket(
|
||||
to = destId,
|
||||
dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
|
||||
bytes = action.emoji.encodeToByteArray(),
|
||||
channel = channel,
|
||||
replyId = action.replyId,
|
||||
wantAck = false,
|
||||
)
|
||||
commandSender.sendData(dataPacket)
|
||||
rememberReaction(action)
|
||||
}
|
||||
is ServiceAction.ImportContact -> {
|
||||
val verifiedContact = action.contact.toBuilder().setManuallyVerified(true).build()
|
||||
commandSender.sendAdmin(myNodeNum) { addContact = verifiedContact }
|
||||
nodeManager.handleReceivedUser(
|
||||
verifiedContact.nodeNum,
|
||||
verifiedContact.user,
|
||||
manuallyVerified = true,
|
||||
)
|
||||
}
|
||||
is ServiceAction.SendContact -> {
|
||||
commandSender.sendAdmin(myNodeNum) { addContact = action.contact }
|
||||
}
|
||||
is ServiceAction.GetDeviceMetadata -> {
|
||||
commandSender.sendAdmin(action.destNum, wantResponse = true) { getDeviceMetadataRequest = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun rememberReaction(action: ServiceAction.Reaction) {
|
||||
scope.handledLaunch {
|
||||
val reaction =
|
||||
ReactionEntity(
|
||||
replyId = action.replyId,
|
||||
userId = DataPacket.ID_LOCAL,
|
||||
emoji = action.emoji,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
snr = 0f,
|
||||
rssi = 0,
|
||||
hopsAway = 0,
|
||||
)
|
||||
packetRepository.get().insertReaction(reaction)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleSetOwner(u: org.meshtastic.core.model.MeshUser, myNodeNum: Int) {
|
||||
commandSender.sendAdmin(myNodeNum) {
|
||||
setOwner = user {
|
||||
id = u.id
|
||||
longName = u.longName
|
||||
shortName = u.shortName
|
||||
isLicensed = u.isLicensed
|
||||
}
|
||||
}
|
||||
nodeManager.handleReceivedUser(
|
||||
myNodeNum,
|
||||
user {
|
||||
id = u.id
|
||||
longName = u.longName
|
||||
shortName = u.shortName
|
||||
isLicensed = u.isLicensed
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun handleSend(p: DataPacket, myNodeNum: Int) {
|
||||
commandSender.sendData(p)
|
||||
serviceBroadcasts.broadcastMessageStatus(p)
|
||||
dataHandler.rememberDataPacket(p, myNodeNum, false)
|
||||
val bytes = p.bytes ?: ByteArray(0)
|
||||
analytics.track("data_send", DataPair("num_bytes", bytes.size), DataPair("type", p.dataType))
|
||||
}
|
||||
|
||||
fun handleRequestPosition(destNum: Int, position: Position, myNodeNum: Int) {
|
||||
if (destNum != myNodeNum) {
|
||||
val provideLocation = meshPrefs.shouldProvideNodeLocation(myNodeNum)
|
||||
val currentPosition =
|
||||
when {
|
||||
provideLocation && position.isValid() -> position
|
||||
else ->
|
||||
nodeManager.nodeDBbyNodeNum[myNodeNum]?.position?.let { Position(it) }?.takeIf { it.isValid() }
|
||||
}
|
||||
currentPosition?.let { commandSender.requestPosition(destNum, it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun handleRemoveByNodenum(nodeNum: Int, requestId: Int, myNodeNum: Int) {
|
||||
nodeManager.removeByNodenum(nodeNum)
|
||||
commandSender.sendAdmin(myNodeNum, requestId) { removeByNodenum = nodeNum }
|
||||
}
|
||||
|
||||
fun handleSetRemoteOwner(id: Int, payload: ByteArray, myNodeNum: Int) {
|
||||
val u = MeshProtos.User.parseFrom(payload)
|
||||
commandSender.sendAdmin(myNodeNum, id) { setOwner = u }
|
||||
}
|
||||
|
||||
fun handleGetRemoteOwner(id: Int, destNum: Int) {
|
||||
commandSender.sendAdmin(destNum, id, wantResponse = true) { getOwnerRequest = true }
|
||||
}
|
||||
|
||||
fun handleSetConfig(payload: ByteArray, myNodeNum: Int) {
|
||||
val c = ConfigProtos.Config.parseFrom(payload)
|
||||
commandSender.sendAdmin(myNodeNum) { setConfig = c }
|
||||
}
|
||||
|
||||
fun handleSetRemoteConfig(id: Int, num: Int, payload: ByteArray) {
|
||||
val c = ConfigProtos.Config.parseFrom(payload)
|
||||
commandSender.sendAdmin(num, id) { setConfig = c }
|
||||
}
|
||||
|
||||
fun handleGetRemoteConfig(id: Int, destNum: Int, config: Int) {
|
||||
commandSender.sendAdmin(destNum, id, wantResponse = true) {
|
||||
if (config == AdminProtos.AdminMessage.ConfigType.SESSIONKEY_CONFIG_VALUE) {
|
||||
getDeviceMetadataRequest = true
|
||||
} else {
|
||||
getConfigRequestValue = config
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleSetModuleConfig(id: Int, num: Int, payload: ByteArray) {
|
||||
val c = ModuleConfigProtos.ModuleConfig.parseFrom(payload)
|
||||
commandSender.sendAdmin(num, id) { setModuleConfig = c }
|
||||
}
|
||||
|
||||
fun handleGetModuleConfig(id: Int, destNum: Int, config: Int) {
|
||||
commandSender.sendAdmin(destNum, id, wantResponse = true) { getModuleConfigRequestValue = config }
|
||||
}
|
||||
|
||||
fun handleSetRingtone(destNum: Int, ringtone: String) {
|
||||
commandSender.sendAdmin(destNum) { setRingtoneMessage = ringtone }
|
||||
}
|
||||
|
||||
fun handleGetRingtone(id: Int, destNum: Int) {
|
||||
commandSender.sendAdmin(destNum, id, wantResponse = true) { getRingtoneRequest = true }
|
||||
}
|
||||
|
||||
fun handleSetCannedMessages(destNum: Int, messages: String) {
|
||||
commandSender.sendAdmin(destNum) { setCannedMessageModuleMessages = messages }
|
||||
}
|
||||
|
||||
fun handleGetCannedMessages(id: Int, destNum: Int) {
|
||||
commandSender.sendAdmin(destNum, id, wantResponse = true) { getCannedMessageModuleMessagesRequest = true }
|
||||
}
|
||||
|
||||
fun handleSetChannel(payload: ByteArray?, myNodeNum: Int) {
|
||||
if (payload != null) {
|
||||
val c = ChannelProtos.Channel.parseFrom(payload)
|
||||
commandSender.sendAdmin(myNodeNum) { setChannel = c }
|
||||
}
|
||||
}
|
||||
|
||||
fun handleSetRemoteChannel(id: Int, num: Int, payload: ByteArray?) {
|
||||
if (payload != null) {
|
||||
val c = ChannelProtos.Channel.parseFrom(payload)
|
||||
commandSender.sendAdmin(num, id) { setChannel = c }
|
||||
}
|
||||
}
|
||||
|
||||
fun handleGetRemoteChannel(id: Int, destNum: Int, index: Int) {
|
||||
commandSender.sendAdmin(destNum, id, wantResponse = true) { getChannelRequest = index + 1 }
|
||||
}
|
||||
|
||||
fun handleRequestNeighborInfo(requestId: Int, destNum: Int) {
|
||||
commandSender.requestNeighborInfo(requestId, destNum)
|
||||
}
|
||||
|
||||
fun handleBeginEditSettings(myNodeNum: Int) {
|
||||
commandSender.sendAdmin(myNodeNum) { beginEditSettings = true }
|
||||
}
|
||||
|
||||
fun handleCommitEditSettings(myNodeNum: Int) {
|
||||
commandSender.sendAdmin(myNodeNum) { commitEditSettings = true }
|
||||
}
|
||||
|
||||
fun handleRebootToDfu(myNodeNum: Int) {
|
||||
commandSender.sendAdmin(myNodeNum) { enterDfuModeRequest = true }
|
||||
}
|
||||
|
||||
fun handleRequestTelemetry(requestId: Int, destNum: Int, type: Int) {
|
||||
commandSender.requestTelemetry(requestId, destNum, type)
|
||||
}
|
||||
|
||||
fun handleRequestShutdown(requestId: Int, destNum: Int) {
|
||||
commandSender.sendAdmin(destNum, requestId) { shutdownSeconds = DEFAULT_REBOOT_DELAY }
|
||||
}
|
||||
|
||||
fun handleRequestReboot(requestId: Int, destNum: Int) {
|
||||
commandSender.sendAdmin(destNum, requestId) { rebootSeconds = DEFAULT_REBOOT_DELAY }
|
||||
}
|
||||
|
||||
fun handleRequestFactoryReset(requestId: Int, destNum: Int) {
|
||||
commandSender.sendAdmin(destNum, requestId) { factoryResetDevice = 1 }
|
||||
}
|
||||
|
||||
fun handleRequestNodedbReset(requestId: Int, destNum: Int, preserveFavorites: Boolean) {
|
||||
commandSender.sendAdmin(destNum, requestId) { nodedbReset = preserveFavorites }
|
||||
}
|
||||
|
||||
fun handleGetDeviceConnectionStatus(requestId: Int, destNum: Int) {
|
||||
commandSender.sendAdmin(destNum, requestId, wantResponse = true) { getDeviceConnectionStatusRequest = true }
|
||||
}
|
||||
|
||||
fun handleUpdateLastAddress(deviceAddr: String?) {
|
||||
val currentAddr = meshPrefs.deviceAddress
|
||||
if (deviceAddr != currentAddr) {
|
||||
meshPrefs.deviceAddress = deviceAddr
|
||||
scope.handledLaunch {
|
||||
nodeManager.clear()
|
||||
databaseManager.switchActiveDatabase(deviceAddr)
|
||||
serviceNotifications.clearNotifications()
|
||||
nodeManager.loadCachedNodeDB()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue