Skip to content

Commit 4833ace

Browse files
jamesarichCopilotclaude
committed
refactor: Remove AIDL API and modernize service architecture (#5586)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent b9a00b4 commit 4833ace

264 files changed

Lines changed: 4203 additions & 6748 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/publish-core.yml

Lines changed: 0 additions & 51 deletions
This file was deleted.

.github/workflows/reusable-check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ jobs:
113113

114114
- name: Lint, Analysis & KMP Smoke Compile
115115
if: inputs.run_lint == true
116-
run: ./gradlew spotlessCheck detekt androidApp:lintFdroidDebug androidApp:lintGoogleDebug core:barcode:lintFdroidDebug core:barcode:lintGoogleDebug core:api:lintDebug kmpSmokeCompile -Pci=true --continue
116+
run: ./gradlew spotlessCheck detekt androidApp:lintFdroidDebug androidApp:lintGoogleDebug core:barcode:lintFdroidDebug core:barcode:lintGoogleDebug kmpSmokeCompile -Pci=true --continue
117117

118118
- name: KMP Smoke Compile (lint skipped)
119119
if: inputs.run_lint == false

.skills/project-overview/SKILL.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Module directory, namespacing conventions, environment setup, and troubleshootin
55

66
- **Build System:** Gradle (Kotlin DSL). JDK 21 REQUIRED. Target SDK: API 36. Min SDK: API 26.
77
- **Flavors:** `fdroid` (OSS only) · `google` (Maps + DataDog analytics)
8-
- **Android-only Modules:** `core:api` (AIDL), `core:barcode` (CameraX). Shared contracts abstracted into `core:ui/commonMain`.
8+
- **Android-only Modules:** `core:barcode` (CameraX). Shared contracts abstracted into `core:ui/commonMain`.
99

1010
## Codebase Map
1111

@@ -28,7 +28,6 @@ Module directory, namespacing conventions, environment setup, and troubleshootin
2828
| `core:navigation` | Shared navigation keys/routes for Navigation 3 using `@Serializable sealed interface` hierarchies. `DeepLinkRouter` for typed backstack synthesis, and `MeshtasticNavSavedStateConfig` with `subclassesOfSealed()` for automatic polymorphic backstack persistence. |
2929
| `core:ui` | Shared Compose UI components (`MeshtasticAppShell`, `MeshtasticNavDisplay`, `MeshtasticNavigationSuite`, `AlertHost`, `SharedDialogs`, `PlaceholderScreen`, `MainAppBar`, dialogs, preferences) and platform abstractions. |
3030
| `core:service` | KMP service layer; Android bindings stay in `androidMain`. |
31-
| `core:api` | Public AIDL/API integration module for external clients. |
3231
| `core:prefs` | KMP preferences layer built on DataStore abstractions. |
3332
| `core:barcode` | Barcode scanning (Android-only). |
3433
| `core:nfc` | NFC abstractions (KMP). Android NFC hardware implementation in `androidMain`. |

.skills/testing-ci/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Run in a single invocation for routine changes to ensure code formatting, analys
1717
> In KMP modules, the `test` task name is **ambiguous**. Gradle matches both `testAndroid` and
1818
> `testAndroidHostTest` and refuses to run either, silently skipping KMP modules.
1919
> `allTests` is the `KotlinTestReport` lifecycle task registered by the KMP plugin.
20-
> Conversely, `allTests` does **not** cover pure-Android modules (`:androidApp`, `:core:api`, etc.), which is why both `test` and `allTests` are needed.
20+
> Conversely, `allTests` does **not** cover pure-Android modules (`:androidApp`, `:core:barcode`, etc.), which is why both `test` and `allTests` are needed.
2121
2222
*Note: If testing Compose UI on the JVM (Robolectric) with Java 21, pin tests to `@Config(sdk = [34])` to avoid SDK 35 compatibility crashes.*
2323

README.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ Each module has its own README with details on its responsibilities, API surface
8787

8888
| Module | Description |
8989
|---|---|
90-
| [core/api](core/api/README.md) | AIDL service API for third-party integrations |
9190
| [core/domain](core/domain/README.md) | Business-logic use cases (radio config, sessions, exports) |
9291
| [core/repository](core/repository/README.md) | Data & infrastructure contracts (RadioTransport, NodeRepository, ServiceRepository) |
9392
| [core/takserver](core/takserver/README.md) | Meshtastic ↔ TAK (ATAK/iTAK) bridge — CoT server & conversion |
@@ -123,13 +122,9 @@ Each module has its own README with details on its responsibilities, API surface
123122

124123
You can help translate the app into your native language using [Crowdin](https://crowdin.meshtastic.org/android).
125124

126-
## API & Integration
125+
## Integration
127126

128-
Developers can integrate with the Meshtastic Android app using our published API library via **JitPack**. This allows third-party applications (like the ATAK plugin) to communicate with the mesh service via AIDL.
129-
130-
For detailed integration instructions, see [core/api/README.md](core/api/README.md).
131-
132-
Additionally, the app includes a built-in **Local TAK Server** feature that can be enabled in settings. This runs a local TCP server on port 8089 to allow ATAK clients to connect directly and route their traffic over the mesh.
127+
The app includes a built-in **Local TAK Server** feature that can be enabled in settings. This runs a local TCP server on port 8089 to allow ATAK clients to connect directly and route their traffic over the mesh.
133128

134129
## Building the Android App
135130
> [!WARNING]

androidApp/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ configure<ApplicationExtension> {
112112
),
113113
)
114114
}
115-
ndk { abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") }
115+
ndk { abiFilters += listOf("armeabi-v7a", "arm64-v8a") }
116116

117117
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
118118
}
@@ -126,7 +126,7 @@ configure<ApplicationExtension> {
126126
abi {
127127
isEnable = !disableSplits
128128
reset()
129-
include("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
129+
include("armeabi-v7a", "arm64-v8a")
130130
isUniversalApk = true
131131
}
132132
}

androidApp/src/fdroid/java/org/meshtastic/app/map/cluster/MarkerClusterer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ protected void hideInfoWindows(){
127127
int zoomLevel = mapView.getZoomLevel();
128128
if (zoomLevel != mLastZoomLevel && !mapView.isAnimating()){
129129
hideInfoWindows();
130-
mClusters = clusterer(mapView);
131-
renderer(mClusters, canvas, mapView);
130+
mClusters = clusterer(mapView);
131+
renderer(mClusters, canvas, mapView);
132132
mLastZoomLevel = zoomLevel;
133133
}
134134

androidApp/src/fdroid/java/org/meshtastic/app/map/cluster/RadiusMarkerClusterer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import android.graphics.drawable.Drawable;
2828
import android.view.MotionEvent;
2929

30+
import androidx.core.content.res.ResourcesCompat;
31+
3032
import org.meshtastic.app.map.model.MarkerWithLabel;
3133

3234
import org.osmdroid.bonuspack.R;
@@ -72,7 +74,7 @@ public RadiusMarkerClusterer(Context ctx) {
7274
mTextPaint.setFakeBoldText(true);
7375
mTextPaint.setTextAlign(Paint.Align.CENTER);
7476
mTextPaint.setAntiAlias(true);
75-
Drawable clusterIconD = ctx.getResources().getDrawable(R.drawable.marker_cluster);
77+
Drawable clusterIconD = ResourcesCompat.getDrawable(ctx.getResources(), R.drawable.marker_cluster, ctx.getTheme());
7678
Bitmap clusterIcon = ((BitmapDrawable) clusterIconD).getBitmap();
7779
setIcon(clusterIcon);
7880
mAnimated = true;

androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapView.kt

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import androidx.compose.runtime.derivedStateOf
5252
import androidx.compose.runtime.getValue
5353
import androidx.compose.runtime.mutableDoubleStateOf
5454
import androidx.compose.runtime.mutableFloatStateOf
55+
import androidx.compose.runtime.mutableIntStateOf
5556
import androidx.compose.runtime.mutableStateListOf
5657
import androidx.compose.runtime.mutableStateOf
5758
import androidx.compose.runtime.remember
@@ -87,6 +88,7 @@ import org.meshtastic.core.common.util.nowMillis
8788
import org.meshtastic.core.common.util.nowSeconds
8889
import org.meshtastic.core.model.DataPacket
8990
import org.meshtastic.core.model.Node
91+
import org.meshtastic.core.model.NodeAddress
9092
import org.meshtastic.core.resources.Res
9193
import org.meshtastic.core.resources.calculating
9294
import org.meshtastic.core.resources.cancel
@@ -113,9 +115,11 @@ import org.meshtastic.core.resources.map_purge_success
113115
import org.meshtastic.core.resources.map_style_selection
114116
import org.meshtastic.core.resources.map_subDescription
115117
import org.meshtastic.core.resources.map_tile_source
118+
import org.meshtastic.core.resources.now
116119
import org.meshtastic.core.resources.only_favorites
117120
import org.meshtastic.core.resources.show_precision_circle
118121
import org.meshtastic.core.resources.show_waypoints
122+
import org.meshtastic.core.resources.unknown
119123
import org.meshtastic.core.resources.waypoint_delete
120124
import org.meshtastic.core.resources.you
121125
import org.meshtastic.core.ui.component.BasicListItem
@@ -239,6 +243,9 @@ fun MapView(
239243
val haptic = LocalHapticFeedback.current
240244
fun performHapticFeedback() = haptic.performHapticFeedback(HapticFeedbackType.LongPress)
241245

246+
val unknownText = stringResource(Res.string.unknown)
247+
val nowText = stringResource(Res.string.now)
248+
242249
// Accompanist permissions state for location
243250
val locationPermissionsState =
244251
rememberMultiplePermissionsState(permissions = listOf(Manifest.permission.ACCESS_FINE_LOCATION))
@@ -355,36 +362,37 @@ fun MapView(
355362

356363
val (p, u) = node.position to node.user
357364
val nodePosition = GeoPoint(node.latitude, node.longitude)
358-
MarkerWithLabel(mapView = this, label = "${u.short_name} ${formatAgo(p.time)}").apply {
359-
id = u.id
360-
title = u.long_name
361-
snippet =
362-
getString(
363-
Res.string.map_node_popup_details,
364-
node.gpsString(),
365-
formatAgo(node.lastHeard),
366-
formatAgo(p.time),
367-
if (node.batteryStr != "") node.batteryStr else "?",
368-
)
369-
ourNode?.distanceStr(node, displayUnits)?.let { dist ->
370-
ourNode.bearing(node)?.let { bearing ->
371-
subDescription = getString(Res.string.map_subDescription, bearing, dist)
365+
MarkerWithLabel(mapView = this, label = "${u.short_name} ${formatAgo(p.time, unknownText, nowText)}")
366+
.apply {
367+
id = u.id
368+
title = u.long_name
369+
snippet =
370+
getString(
371+
Res.string.map_node_popup_details,
372+
node.gpsString(),
373+
formatAgo(node.lastHeard, unknownText, nowText),
374+
formatAgo(p.time, unknownText, nowText),
375+
if (node.batteryStr != "") node.batteryStr else "?",
376+
)
377+
ourNode?.distanceStr(node, displayUnits)?.let { dist ->
378+
ourNode.bearing(node)?.let { bearing ->
379+
subDescription = getString(Res.string.map_subDescription, bearing, dist)
380+
}
381+
}
382+
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
383+
position = nodePosition
384+
icon = markerIcon
385+
setNodeColors(node.colors)
386+
if (!mapFilterStateValue.showPrecisionCircle) {
387+
setPrecisionBits(0)
388+
} else {
389+
setPrecisionBits(p.precision_bits)
390+
}
391+
setOnLongClickListener {
392+
navigateToNodeDetails(node.num)
393+
true
372394
}
373395
}
374-
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
375-
position = nodePosition
376-
icon = markerIcon
377-
setNodeColors(node.colors)
378-
if (!mapFilterStateValue.showPrecisionCircle) {
379-
setPrecisionBits(0)
380-
} else {
381-
setPrecisionBits(p.precision_bits)
382-
}
383-
setOnLongClickListener {
384-
navigateToNodeDetails(node.num)
385-
true
386-
}
387-
}
388396
}
389397
}
390398

@@ -433,7 +441,7 @@ fun MapView(
433441
}
434442
}
435443

436-
fun getUsername(id: String?) = if (id == DataPacket.ID_LOCAL || (myId != null && id == myId)) {
444+
fun getUsername(id: String?) = if (id == NodeAddress.ID_LOCAL || (myId != null && id == myId)) {
437445
getString(Res.string.you)
438446
} else {
439447
mapViewModel.getUser(id).long_name
@@ -446,7 +454,7 @@ fun MapView(
446454
if (!mapFilterState.showWaypoints) return@mapNotNull null // Use collected mapFilterState
447455
val lock = if (pt.locked_to != 0) "\uD83D\uDD12" else ""
448456
val time = DateFormatter.formatDateTime(waypoint.time)
449-
val label = pt.name + " " + formatAgo((waypoint.time / 1000).toInt())
457+
val label = pt.name + " " + formatAgo((waypoint.time / 1000).toInt(), unknownText, nowText)
450458
val emoji = String(Character.toChars(if (pt.icon == 0) 128205 else pt.icon))
451459
val now = nowMillis
452460
val expireTimeMillis = pt.expire * 1000L
@@ -818,15 +826,15 @@ private fun FdroidMainMapFilterDropdown(
818826

819827
@Composable
820828
private fun MapStyleDialog(selectedMapStyle: Int, onDismiss: () -> Unit, onSelectMapStyle: (Int) -> Unit) {
821-
val selected = remember { mutableStateOf(selectedMapStyle) }
829+
val selected = remember { mutableIntStateOf(selectedMapStyle) }
822830

823831
MapsDialog(onDismiss = onDismiss) {
824832
CustomTileSource.mTileSources.values.forEachIndexed { index, style ->
825833
ListItem(
826834
text = style,
827-
trailingIcon = if (index == selected.value) MeshtasticIcons.Check else null,
835+
trailingIcon = if (index == selected.intValue) MeshtasticIcons.Check else null,
828836
onClick = {
829-
selected.value = index
837+
selected.intValue = index
830838
onSelectMapStyle(index)
831839
onDismiss()
832840
},
@@ -879,7 +887,7 @@ private fun PurgeTileSourceDialog(onDismiss: () -> Unit) {
879887
val context = LocalContext.current
880888
val cache = SqlTileWriterExt()
881889

882-
val sourceList by derivedStateOf { cache.sources.map { it.source as String } }
890+
val sourceList by remember { derivedStateOf { cache.sources.map { it.source as String } } }
883891

884892
val selected = remember { mutableStateListOf<Int>() }
885893

androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ import kotlinx.coroutines.flow.StateFlow
2222
import kotlinx.coroutines.flow.asStateFlow
2323
import org.koin.core.annotation.KoinViewModel
2424
import org.meshtastic.core.common.BuildConfigProvider
25-
import org.meshtastic.core.model.RadioController
2625
import org.meshtastic.core.repository.MapPrefs
2726
import org.meshtastic.core.repository.NodeRepository
2827
import org.meshtastic.core.repository.PacketRepository
2928
import org.meshtastic.core.repository.RadioConfigRepository
29+
import org.meshtastic.core.repository.RadioController
3030
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
3131
import org.meshtastic.feature.map.BaseMapViewModel
3232
import org.meshtastic.proto.LocalConfig

0 commit comments

Comments
 (0)