refactor(core:service): Move Android Service implementations to core:service

This commit is contained in:
James Rich 2026-03-17 10:57:00 -05:00
parent 7593d2b0a4
commit 965def06b2
25 changed files with 57 additions and 50 deletions

View file

@ -152,7 +152,7 @@
<!-- This is the public API for doing mesh radio operations from android apps -->
<service
android:name="org.meshtastic.app.service.MeshService"
android:name="org.meshtastic.core.service.MeshService"
android:enabled="true"
android:foregroundServiceType="connectedDevice|location"
android:exported="true" tools:ignore="ExportedActivity">
@ -228,7 +228,7 @@
android:resource="@xml/device_filter" />
</activity>
<receiver android:name="org.meshtastic.app.service.BootCompleteReceiver"
<receiver android:name="org.meshtastic.core.service.BootCompleteReceiver"
android:exported="false">
<!-- handle boot events -->
<intent-filter>
@ -252,9 +252,9 @@
android:path="com.geeksville.mesh" /> -->
</intent-filter>
</receiver>
<receiver android:name="org.meshtastic.app.service.ReplyReceiver" android:exported="false" />
<receiver android:name="org.meshtastic.app.service.MarkAsReadReceiver" android:exported="false" />
<receiver android:name="org.meshtastic.app.service.ReactionReceiver" android:exported="false" />
<receiver android:name="org.meshtastic.core.service.ReplyReceiver" android:exported="false" />
<receiver android:name="org.meshtastic.core.service.MarkAsReadReceiver" android:exported="false" />
<receiver android:name="org.meshtastic.core.service.ReactionReceiver" android:exported="false" />
<receiver
android:name="org.meshtastic.app.widget.LocalStatsWidgetReceiver"

View file

@ -25,8 +25,8 @@ import androidx.lifecycle.lifecycleScope
import co.touchlab.kermit.Logger
import kotlinx.coroutines.launch
import org.koin.core.annotation.Factory
import org.meshtastic.app.service.MeshService
import org.meshtastic.app.service.startService
import org.meshtastic.core.service.MeshService
import org.meshtastic.core.service.startService
import org.meshtastic.core.common.util.SequentialJob
import org.meshtastic.core.service.AndroidServiceRepository
import org.meshtastic.core.service.BindFailedException

View file

@ -40,7 +40,7 @@ import org.koin.core.context.startKoin
import org.meshtastic.app.di.AppKoinModule
import org.meshtastic.app.di.module
import org.meshtastic.app.widget.LocalStatsWidgetReceiver
import org.meshtastic.app.worker.MeshLogCleanupWorker
import org.meshtastic.core.service.worker.MeshLogCleanupWorker
import org.meshtastic.core.common.ContextServices
import org.meshtastic.core.database.DatabaseManager
import org.meshtastic.core.repository.MeshPrefs

View file

@ -18,6 +18,7 @@ package org.meshtastic.app.messaging.domain.worker
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import org.meshtastic.core.service.worker.SendMessageWorker
import androidx.work.WorkManager
import androidx.work.workDataOf
import org.koin.core.annotation.Single

View file

@ -74,7 +74,7 @@ import org.meshtastic.app.navigation.firmwareGraph
import org.meshtastic.app.navigation.mapGraph
import org.meshtastic.app.navigation.nodesGraph
import org.meshtastic.app.navigation.settingsGraph
import org.meshtastic.app.service.MeshService
import org.meshtastic.core.service.MeshService
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.DeviceType
import org.meshtastic.core.model.DeviceVersion

View file

@ -4,5 +4,5 @@ This file tracks all major tracks for the project. Each track has its own detail
---
- [ ] **Track: Extract service/worker/radio files from `app` to `core:service/androidMain` and `core:network/androidMain`**
- [~] **Track: Extract service/worker/radio files from `app` to `core:service/androidMain` and `core:network/androidMain`**
*Link: [./tracks/extract_services_20260317/](./tracks/extract_services_20260317/)*

View file

@ -9,7 +9,7 @@
## Phase 2: Extraction to `core:service`
- [x] Task: Setup `core:service` module for Android and Common targets (if not already fully configured) [a114084]
- [ ] Task: Move Android `Service` implementations to `core:service/androidMain`
- [~] Task: Move Android `Service` implementations to `core:service/androidMain`
- [ ] Move the files
- [ ] Update imports and Koin injections
- [ ] Task: Abstract shared service logic into `core:service/commonMain`

View file

@ -52,4 +52,9 @@ data class DeviceVersion(val asString: String) : Comparable<DeviceVersion> {
}
override fun compareTo(other: DeviceVersion): Int = asInt.compareTo(other.asInt)
companion object {
const val MIN_FW_VERSION = "2.5.14"
const val ABS_MIN_FW_VERSION = "2.3.15"
}
}

View file

@ -36,6 +36,7 @@ kotlin {
implementation(projects.core.data)
implementation(projects.core.database)
implementation(projects.core.model)
implementation(projects.core.navigation)
implementation(projects.core.prefs)
implementation(projects.core.proto)

View file

@ -14,14 +14,14 @@
* 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 org.meshtastic.app.service
package org.meshtastic.core.service
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import org.koin.core.annotation.Single
import org.meshtastic.app.messaging.domain.worker.SendMessageWorker
import org.meshtastic.core.service.worker.SendMessageWorker
import org.meshtastic.core.repository.MeshWorkerManager
@Single

View file

@ -200,7 +200,7 @@ class AndroidRadioControllerImpl(
// Ensure service is running/restarted to handle the new address
val intent =
android.content.Intent().apply {
setClassName("com.geeksville.mesh", "org.meshtastic.app.service.MeshService")
setClassName("com.geeksville.mesh", "org.meshtastic.core.service.MeshService")
}
context.startForegroundService(intent)
}

View file

@ -14,7 +14,7 @@
* 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 org.meshtastic.app.service
package org.meshtastic.core.service
import android.content.BroadcastReceiver
import android.content.Context

View file

@ -14,7 +14,7 @@
* 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 org.meshtastic.app.service
package org.meshtastic.core.service
import org.meshtastic.core.api.MeshtasticIntent

View file

@ -14,7 +14,7 @@
* 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 org.meshtastic.app.service
package org.meshtastic.core.service
import android.content.BroadcastReceiver
import android.content.Context

View file

@ -14,7 +14,7 @@
* 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 org.meshtastic.app.service
package org.meshtastic.core.service
import android.app.Service
import android.content.Context
@ -30,7 +30,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.koin.android.ext.android.inject
import org.meshtastic.app.BuildConfig
import org.meshtastic.core.common.hasLocationPermission
import org.meshtastic.core.common.util.handledLaunch
import org.meshtastic.core.common.util.toRemoteExceptions
@ -54,7 +54,7 @@ import org.meshtastic.core.repository.SERVICE_NOTIFY_ID
import org.meshtastic.core.repository.ServiceBroadcasts
import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.core.service.IMeshService
import org.meshtastic.feature.connections.NO_DEVICE_SELECTED
import org.meshtastic.proto.PortNum
@Suppress("TooManyFunctions", "LargeClass")
@ -102,8 +102,8 @@ class MeshService : Service() {
startService(context)
}
val minDeviceVersion = DeviceVersion(BuildConfig.MIN_FW_VERSION)
val absoluteMinDeviceVersion = DeviceVersion(BuildConfig.ABS_MIN_FW_VERSION)
val minDeviceVersion = DeviceVersion(DeviceVersion.MIN_FW_VERSION)
val absoluteMinDeviceVersion = DeviceVersion(DeviceVersion.ABS_MIN_FW_VERSION)
}
override fun onCreate() {
@ -143,7 +143,7 @@ class MeshService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val a = radioInterfaceService.getDeviceAddress()
val wantForeground = a != null && a != NO_DEVICE_SELECTED
val wantForeground = a != null && a != "n"
val notification = connectionManager.updateStatusNotification() as android.app.Notification

View file

@ -14,7 +14,7 @@
* 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 org.meshtastic.app.service
package org.meshtastic.core.service
import android.app.Notification
import android.app.NotificationChannel
@ -40,11 +40,11 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.jetbrains.compose.resources.StringResource
import org.koin.core.annotation.Single
import org.meshtastic.app.MainActivity
import org.meshtastic.app.R.raw
import org.meshtastic.app.service.MarkAsReadReceiver.Companion.MARK_AS_READ_ACTION
import org.meshtastic.app.service.ReactionReceiver.Companion.REACT_ACTION
import org.meshtastic.app.service.ReplyReceiver.Companion.KEY_TEXT_REPLY
import org.meshtastic.core.resources.R.raw
import org.meshtastic.core.service.MarkAsReadReceiver.Companion.MARK_AS_READ_ACTION
import org.meshtastic.core.service.ReactionReceiver.Companion.REACT_ACTION
import org.meshtastic.core.service.ReplyReceiver.Companion.KEY_TEXT_REPLY
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.Message
@ -453,7 +453,7 @@ class MeshServiceNotificationsImpl(
val summaryNotification =
commonBuilder(NotificationType.DirectMessage)
.setSmallIcon(org.meshtastic.app.R.drawable.app_icon)
.setSmallIcon(context.applicationInfo.icon)
.setStyle(messagingStyle)
.setGroup(GROUP_KEY_MESSAGES)
.setGroupSummary(true)
@ -697,14 +697,14 @@ class MeshServiceNotificationsImpl(
// region Helper/Builder Methods
private val openAppIntent: PendingIntent by lazy {
val intent = Intent(context, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP }
val intent = Intent(context, Class.forName("org.meshtastic.app.MainActivity")).apply { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP }
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun createOpenMessageIntent(contactKey: String): PendingIntent {
val deepLinkUri = "$DEEP_LINK_BASE_URI/messages/$contactKey".toUri()
val deepLinkIntent =
Intent(Intent.ACTION_VIEW, deepLinkUri, context, MainActivity::class.java).apply {
Intent(Intent.ACTION_VIEW, deepLinkUri, context, Class.forName("org.meshtastic.app.MainActivity")).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
@ -717,7 +717,7 @@ class MeshServiceNotificationsImpl(
private fun createOpenWaypointIntent(waypointId: Int): PendingIntent {
val deepLinkUri = "$DEEP_LINK_BASE_URI/map?waypointId=$waypointId".toUri()
val deepLinkIntent =
Intent(Intent.ACTION_VIEW, deepLinkUri, context, MainActivity::class.java).apply {
Intent(Intent.ACTION_VIEW, deepLinkUri, context, Class.forName("org.meshtastic.app.MainActivity")).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
@ -730,7 +730,7 @@ class MeshServiceNotificationsImpl(
private fun createOpenNodeDetailIntent(nodeNum: Int): PendingIntent {
val deepLinkUri = "$DEEP_LINK_BASE_URI/node?destNum=$nodeNum".toUri()
val deepLinkIntent =
Intent(Intent.ACTION_VIEW, deepLinkUri, context, MainActivity::class.java).apply {
Intent(Intent.ACTION_VIEW, deepLinkUri, context, Class.forName("org.meshtastic.app.MainActivity")).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
@ -811,7 +811,7 @@ class MeshServiceNotificationsImpl(
type: NotificationType,
contentIntent: PendingIntent? = null,
): NotificationCompat.Builder {
val smallIcon = org.meshtastic.app.R.drawable.app_icon
val smallIcon = context.applicationInfo.icon
return NotificationCompat.Builder(context, type.channelId)
.setSmallIcon(smallIcon)

View file

@ -14,7 +14,7 @@
* 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 org.meshtastic.app.service
package org.meshtastic.core.service
import android.app.ForegroundServiceStartNotAllowedException
import android.content.Context
@ -23,8 +23,8 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager
import co.touchlab.kermit.Logger
import org.meshtastic.app.BuildConfig
import org.meshtastic.app.worker.ServiceKeepAliveWorker
import org.meshtastic.core.service.worker.ServiceKeepAliveWorker
// / Helper function to start running our service
fun MeshService.Companion.startService(context: Context) {
@ -36,7 +36,7 @@ fun MeshService.Companion.startService(context: Context) {
// Before binding we want to explicitly create - so the service stays alive forever (so it can keep
// listening for the bluetooth packets arriving from the radio. And when they arrive forward them
// to Signal or whatever.
Logger.i { "Trying to start service debug=${BuildConfig.DEBUG}" }
Logger.i { "Trying to start service debug=${false}" }
val intent = createIntent(context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {

View file

@ -14,7 +14,7 @@
* 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 org.meshtastic.app.service
package org.meshtastic.core.service
import android.content.BroadcastReceiver
import android.content.Context

View file

@ -14,7 +14,7 @@
* 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 org.meshtastic.app.service
package org.meshtastic.core.service
import android.content.BroadcastReceiver
import android.content.Context

View file

@ -14,7 +14,7 @@
* 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 org.meshtastic.app.service
package org.meshtastic.core.service
import android.content.Context
import android.content.Intent

View file

@ -14,7 +14,7 @@
* 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 org.meshtastic.app.worker
package org.meshtastic.core.service.worker
import android.content.Context
import androidx.work.CoroutineWorker

View file

@ -14,7 +14,7 @@
* 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 org.meshtastic.app.messaging.domain.worker
package org.meshtastic.core.service.worker
import android.content.Context
import androidx.work.CoroutineWorker

View file

@ -14,7 +14,7 @@
* 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 org.meshtastic.app.worker
package org.meshtastic.core.service.worker
import android.app.Notification
import android.content.Context
@ -26,9 +26,9 @@ import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import co.touchlab.kermit.Logger
import org.koin.android.annotation.KoinWorker
import org.meshtastic.app.R
import org.meshtastic.app.service.MeshService
import org.meshtastic.app.service.startService
import org.meshtastic.core.service.MeshService
import org.meshtastic.core.service.startService
import org.meshtastic.core.repository.MeshServiceNotifications
import org.meshtastic.core.repository.SERVICE_NOTIFY_ID
@ -81,7 +81,7 @@ class ServiceKeepAliveWorker(
// We use "my_service" which matches NotificationType.ServiceState.channelId in MeshServiceNotificationsImpl
return NotificationCompat.Builder(applicationContext, "my_service")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setSmallIcon(applicationContext.applicationInfo.icon)
.setContentTitle("Resuming Mesh Service")
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true)

View file

@ -134,7 +134,7 @@ class MainActivity : ComponentActivity() {
Log.i(TAG, "Found service in package: ${serviceInfo.packageName}")
} else {
Log.w(TAG, "No service found for action com.geeksville.mesh.Service. Falling back to default.")
intent.setClassName("com.geeksville.mesh", "org.meshtastic.app.service.MeshService")
intent.setClassName("com.geeksville.mesh", "org.meshtastic.core.service.MeshService")
}
val success = bindService(intent, serviceConnection, BIND_AUTO_CREATE)