Skip to content

refactor: Remove AIDL API and modernize service architecture#5586

Open
jamesarich wants to merge 8 commits into
mainfrom
jamesarich/remove-aidl-api
Open

refactor: Remove AIDL API and modernize service architecture#5586
jamesarich wants to merge 8 commits into
mainfrom
jamesarich/remove-aidl-api

Conversation

@jamesarich
Copy link
Copy Markdown
Collaborator

@jamesarich jamesarich commented May 23, 2026

Summary

Removes the deprecated AIDL bound-service API and all associated infrastructure, replacing it with a direct suspend-based SDK architecture. This eliminates ~3,200 lines of indirection while preserving identical runtime behavior.

Changes

AIDL Removal

  • Deleted core:api module (AIDL interfaces, bound service, action routing)
  • Removed IMeshService.aidl, service bindings, and broadcast-based command dispatch
  • Replaced with direct RadioController suspend calls from ViewModels/use cases

Architecture Modernization

  • NodeAddress sealed class — type-safe node addressing replaces raw Int/String
  • CommandSender — structured command origin tracking
  • Merged MeshActionHandler into DirectRadioControllerImpl
  • Converted all service actions to suspend functions with structured concurrency
  • Eliminated ServiceAction enum and intent-based routing

Build System Cleanup

  • Removed publishing infrastructure (PublishingConventionPlugin, publish-core.yml)
  • Unified all modules to JDK 21 (no split target for published modules)
  • Removed redundant OptimizeNonSkippingGroups compiler flag
  • Removed stale x86/x86_64 ABI filters

Code Quality

  • Eliminated runBlocking from UI rendering path (formatAgo dual-overload)
  • Fixed all lint errors (StateFlow.value in composition, unremembered derivedStateOf, autoboxing)
  • Replaced deprecated getDrawable() with ResourcesCompat.getDrawable()
  • Removed unused dependencies (takpacket-sdk-jvm)

Stats

  • 249 files changed, +3,126 / −6,367 (net −3,241 lines)
  • 0 lint errors, 10 non-actionable warnings
  • All tests pass (708 test tasks executed)

Testing

  • Full ./gradlew spotlessApply detekt assembleDebug test allTests passes
  • New unit tests for NodeAddress and CommandSender
  • Existing test suite validates no behavioral regressions

@github-actions github-actions Bot added build Build system changes refactor no functional changes repo Repository maintenance labels May 23, 2026
@jamesarich jamesarich marked this pull request as ready for review May 23, 2026 13:44
@jamesarich jamesarich changed the title refactor: remove ServiceBroadcasts + CommonParcelable infrastructure refactor: remove deprecated AIDL API and broadcast infrastructure May 23, 2026
@jamesarich jamesarich force-pushed the jamesarich/remove-aidl-api branch from 2777fe8 to 3fde9cc Compare May 23, 2026 13:55
@jamesarich jamesarich marked this pull request as draft May 23, 2026 13:57
@jamesarich jamesarich force-pushed the jamesarich/remove-aidl-api branch from 3fde9cc to b304b0d Compare May 23, 2026 14:00
@codecov
Copy link
Copy Markdown

codecov Bot commented May 23, 2026

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
2493 1 2492 0
View the top 1 failed test(s) by shortest run time
org.meshtastic.core.database.DatabaseManagerWithDbRetryTest::withDb retries against current database when previous pool closes during switch
Stack Traces | 20.2s run time
java.lang.AssertionError: expected:<42424242> but was:<null>
	at org.junit.Assert.fail(Assert.java:89)
	at org.junit.Assert.failNotEquals(Assert.java:835)
	at org.junit.Assert.assertEquals(Assert.java:120)
	at kotlin.test.junit.JUnitAsserter.assertEquals(JUnitSupport.kt:32)
	at kotlin.test.AssertionsKt__AssertionsKt.assertEquals(Assertions.kt:63)
	at kotlin.test.AssertionsKt.assertEquals(Unknown Source)
	at kotlin.test.AssertionsKt__AssertionsKt.assertEquals$default(Assertions.kt:62)
	at kotlin.test.AssertionsKt.assertEquals$default(Unknown Source)
	at org.meshtastic.core.database.DatabaseManagerWithDbRetryTest$withDb retries against current database when previous pool closes during switch$1.invokeSuspend(DatabaseManagerWithDbRetryTest.kt:99)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.test.TestDispatcher.processEvent$kotlinx_coroutines_test(TestDispatcher.kt:24)
	at kotlinx.coroutines.test.TestCoroutineScheduler.tryRunNextTaskUnless$kotlinx_coroutines_test(TestCoroutineScheduler.kt:98)
	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$2$1$workRunner$1.invokeSuspend(TestBuilders.kt:326)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:256)
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:54)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlockingImpl(Builders.kt:30)
	at kotlinx.coroutines.BuildersKt.runBlockingImpl(Unknown Source)
	at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingK(Builders.concurrent.kt:172)
	at kotlinx.coroutines.BuildersKt.runBlockingK(Unknown Source)
	at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingK$default(Builders.concurrent.kt:157)
	at kotlinx.coroutines.BuildersKt.runBlockingK$default(Unknown Source)
	at kotlinx.coroutines.test.TestBuildersJvmKt.createTestResult(TestBuildersJvm.kt:10)
	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest-8Mi8wO0(TestBuilders.kt:309)
	at kotlinx.coroutines.test.TestBuildersKt.runTest-8Mi8wO0(TestBuilders.kt:1)
	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest-8Mi8wO0(TestBuilders.kt:167)
	at kotlinx.coroutines.test.TestBuildersKt.runTest-8Mi8wO0(TestBuilders.kt:1)
	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest-8Mi8wO0$default(TestBuilders.kt:159)
	at kotlinx.coroutines.test.TestBuildersKt.runTest-8Mi8wO0$default(TestBuilders.kt:1)
	at org.meshtastic.core.database.DatabaseManagerWithDbRetryTest.withDb retries against current database when previous pool closes during switch(DatabaseManagerWithDbRetryTest.kt:69)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:524)
	at org.robolectric.internal.SandboxTestRunner.executeInSandbox(SandboxTestRunner.java:494)
	at org.robolectric.internal.SandboxTestRunner.access$900(SandboxTestRunner.java:67)
	at org.robolectric.internal.SandboxTestRunner$7.evaluate(SandboxTestRunner.java:442)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.robolectric.internal.SandboxTestRunner.access$600(SandboxTestRunner.java:67)
	at org.robolectric.internal.SandboxTestRunner$6.evaluate(SandboxTestRunner.java:333)
	at org.robolectric.internal.SandboxTestRunner$3.evaluate(SandboxTestRunner.java:233)
	at org.robolectric.internal.SandboxTestRunner$5.lambda$evaluate$0(SandboxTestRunner.java:317)
	at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:101)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
	at java.base/java.lang.Thread.run(Thread.java:1583)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 23, 2026

📄 Docs staleness check — advisory

This PR modifies user-facing UI source files but does not update any page under docs/en/user/ or docs/en/developer/.

⚠️ Doc changes propagate to 3 consumers: in-app docs browser, Jekyll site (GitHub Pages), and meshtastic.org (Docusaurus sync). Updating a page in docs/en/ automatically flows to all three.

Changed source files:

core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/BuildNodeDescription.kt
core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/share/SharedContactViewModel.kt
core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/FormatAgo.kt
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageScreenComponents.kt
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/Reaction.kt
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/ContactsViewModel.kt
feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/NodeDetailsSection.kt

What to check:

Changed area Likely doc page
feature/messaging/ docs/en/user/messages-and-channels.md
feature/node/ docs/en/user/nodes.md or docs/en/user/node-metrics.md
feature/map/ docs/en/user/map-and-waypoints.md
feature/connections/ docs/en/user/connections.md
feature/settings/ docs/en/user/settings-radio-user.md or docs/en/user/settings-module-admin.md
feature/firmware/ docs/en/user/firmware.md
feature/intro/ docs/en/user/onboarding.md
feature/discovery/ docs/en/user/discovery.md
feature/docs/ Internal docs infrastructure
core/ui/ docs/en/developer/codebase.md or component-specific user pages

New page checklist (if adding a new doc page):

  1. Create the .md file in docs/en/user/ or docs/en/developer/ with last_updated frontmatter
  2. Register in DocBundleLoader.kt with string resources (in-app browser)
  3. Jekyll and Docusaurus sync pick up new pages automatically — no config change needed

If this PR does not require a doc update (e.g., internal refactor, bug fix, test change), add the skip-docs-check label to dismiss this check.

Cross-platform note: This check is advisory while doc coverage matures. Both Android and Apple repos use the same skip-docs-check label and advisory severity. See meshtastic/design standards for shared conventions.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 23, 2026

🖼️ Preview staleness check — advisory

This PR modifies UI composables but does not update any *Previews.kt files.

Previews power screenshot tests and in-app docs screenshots. Keeping them current ensures visual regression coverage stays accurate.

Changed UI files:

feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageScreenComponents.kt
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/Reaction.kt
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/ContactsViewModel.kt
feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/NodeDetailsSection.kt

What to check:

Pattern Preview file convention
feature/{name}/…/ui/ or component/ feature/{name}/…/*Previews.kt
core/ui/…/ core/ui/…/ (previews colocated)

Adding previews checklist:

  1. Create or update a *Previews.kt file in the same module with @PreviewLightDark
  2. Add @Suppress("PreviewPublic") if the preview is consumed by screenshot-tests
  3. Add corresponding @PreviewTest function in screenshot-tests/src/screenshotTest/
  4. Run ./gradlew :screenshot-tests:updateDebugScreenshotTest to generate reference images

If this PR does not require preview updates (e.g., logic-only change, non-visual refactor), add the skip-preview-check label to dismiss.

@jamesarich jamesarich force-pushed the jamesarich/remove-aidl-api branch from 51fff3c to 6f447c8 Compare May 23, 2026 18:47
@jamesarich jamesarich changed the title refactor: remove deprecated AIDL API and broadcast infrastructure refactor: remove AIDL API and modernize radio architecture May 23, 2026
@jamesarich jamesarich force-pushed the jamesarich/remove-aidl-api branch 4 times, most recently from 237f5e1 to 059ee97 Compare May 23, 2026 21:21
@jamesarich jamesarich changed the title refactor: remove AIDL API and modernize radio architecture refactor: Remove AIDL API and modernize service architecture May 26, 2026
@jamesarich jamesarich marked this pull request as ready for review May 26, 2026 20:36
jamesarich and others added 8 commits May 27, 2026 21:17
Remove the deprecated AIDL/IPC API surface and perform deep architectural
modernization of the radio command pipeline, aligning with the meshtastic-sdk
AdminApiImpl pattern for future SDK migration.

Key changes:

1. AIDL Removal & Infrastructure Cleanup
   - Delete core:api module and all AIDL interfaces
   - Remove ServiceBroadcasts + CommonParcelable infrastructure
   - Remove core:api from CI workflow lint/publish steps

2. Model Modernization
   - Introduce NodeAddress sealed class with type-safe addressing
   - Remove deprecated DataPacket constants in favor of NodeAddress
   - Consolidate dual node maps into single source with getNodeById
   - Split large model files, deduplicate NodeEntity, flatten RadioController

3. Service Layer Refactoring (SDK-aligned)
   - Remove ServiceAction sealed class, use direct suspend calls
   - Convert CommandSender & MeshActionHandler to suspend APIs
   - Merge MeshActionHandler into DirectRadioControllerImpl
     (ViewModel → RadioController → CommandSender, no intermediate layer)
   - Build AdminMessage protos directly with typed protos end-to-end
   - Apply structured concurrency to NodeRequestActions/NodeManagementActions
   - Fix CancellationException handling throughout

Architecture (before → after):
  ViewModel → RadioController → Handler → CommandSender → PacketHandler
  ViewModel → RadioController → CommandSender → PacketHandler

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- NodeAddressTest: 37 tests covering sealed class parser, roundtrip,
  extensions (ContactKey, DataPacket), edge cases
- CommandSenderImplTest: 20 tests covering packet ID generation,
  address resolution, sendData validation, admin messages, position
- DirectRadioControllerImplTest: +8 tests for reboot/shutdown/factory
  reset, importContact, refreshMetadata
- NodeManagerImplTest: +7 tests for getMyNodeInfo aggregation, getMyId,
  null telemetry handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
With AGP 9's built-in Kotlin, org.jetbrains.kotlin.android is no longer
applied to Android modules. Convention plugin hooks using
withPlugin("org.jetbrains.kotlin.android") were dead code — replaced
with withPlugin("com.android.application") and
withPlugin("com.android.library") which correctly trigger for
Android-only modules using built-in Kotlin.

- KoinConventionPlugin: split into app + library hooks
- AndroidRoomConventionPlugin: use com.android.library hook
- KotlinXSerializationConventionPlugin: use app + library hooks
- Remove kotlin-android from version catalog and root build.gradle.kts

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
No modules are published externally anymore (core:api was removed).
Remove all publishing-related configuration:

- Delete PublishingConventionPlugin and its registration
- Remove publish-core.yml workflow
- Strip publishing{} blocks from core/model and core/proto
- Remove PUBLISHED_MODULES set and Java 17 compatibility logic
- Unify all modules to JDK 21 toolchain and JVM target
- Remove unused imports (JavaToolchainService, JavaLanguageVersion, Test)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove x86/x86_64 from ABI filters (armeabi-v7a/arm64-v8a only)
- Remove unused takpacket-sdk-jvm from version catalog
- Raise core/proto minSdk from 21 to 26 (ATAK compat no longer needed)
- Remove stale FIXME comment about foreground service in manifest
- Replace Executors.newSingleThreadExecutor with Dispatchers.Default.asExecutor
  in BarcodeScannerProvider (removes manual thread pool management)
- Convert formatAgo() to @composable with stringResource(), eliminating
  runBlocking from the UI rendering path. Non-composable callers (map views,
  accessibility) use a 3-arg overload with pre-resolved strings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Wrap StateFlow.value read in remember{} to satisfy composition lint
- Wrap derivedStateOf in remember{} (MapView PurgeTileSourceDialog)
- Use mutableIntStateOf to avoid autoboxing (MapStyleDialog)
- Use ResourcesCompat.getDrawable() instead of deprecated getDrawable()
- Fix mixed indentation in MarkerClusterer.java

Lint result: 0 errors, 10 warnings (down from 2 errors, 12 warnings)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This feature flag is now enabled by default in the Compose compiler,
making the explicit opt-in unnecessary.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jamesarich jamesarich force-pushed the jamesarich/remove-aidl-api branch from f475045 to 66ae840 Compare May 28, 2026 02:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build Build system changes refactor no functional changes repo Repository maintenance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant