Skip to content

Commit 6bfa5b5

Browse files
authored
refactor(ble): Centralize BLE logic into a core module (#4550)
Signed-off-by: James Rich <[email protected]>
1 parent 7a68802 commit 6bfa5b5

File tree

214 files changed

+3473
-2407
lines changed

Some content is hidden

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

214 files changed

+3473
-2407
lines changed

AGENTS.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This file serves as a comprehensive guide for AI agents and developers working o
1919
| :--- | :--- |
2020
| `app/` | Main application module. Contains `MainActivity`, `AppNavigation`, and Hilt entry points. Uses package `com.geeksville.mesh`. |
2121
| `core/` | Shared library modules. Most code here uses package `org.meshtastic.core.*`. |
22+
| `core/ble/` | **New:** Bluetooth Low Energy stack using Nordic libraries. |
2223
| `core/strings/` | **Crucial:** Centralized string resources using Compose Multiplatform Resources. |
2324
| `feature/` | Feature modules (e.g., `settings`, `map`, `messaging`). Each is a standalone Gradle module. Uses package `org.meshtastic.feature.*`. |
2425
| `build-logic/` | Custom Gradle convention plugins. Defines build logic for the entire project. |
@@ -58,13 +59,20 @@ This file serves as a comprehensive guide for AI agents and developers working o
5859
- Routes are defined in `core/navigation` (e.g., `ContactsRoutes`, `SettingsRoutes`).
5960
- The main `NavHost` is located in `app/src/main/java/com/geeksville/mesh/ui/Main.kt`.
6061

61-
### D. Dependency Management
62+
### D. Bluetooth (BLE)
63+
- **Library:** Uses **Nordic Semiconductor's Kotlin BLE Library** and **Android Common Libraries**.
64+
- **Location:** Core logic resides in `core/ble`.
65+
- **Key Classes:** `BluetoothRepository`, `NordicBleInterface`, `BleConnection`.
66+
- **Usage:** Use `BluetoothRepository` for scanning and bonding. Use `BleConnection` for managing connections. Avoid legacy `BluetoothAdapter` APIs directly.
67+
- **Environment Mocking:** Use `LocalEnvironmentOwner` and `MockAndroidEnvironment` to test UI hardware reactions without a real device.
68+
69+
### E. Dependency Management
6270
- **Never** hardcode versions in `build.gradle.kts` files.
6371
- **Action:** Add the library and version to `gradle/libs.versions.toml`.
6472
- **Action:** Apply plugins using the alias from the catalog (e.g., `alias(libs.plugins.meshtastic.android.library)`).
6573
- **Alpha Libraries:** Do not be shy about using alpha libraries from Google if they provide necessary features.
6674
67-
### E. Build Variants (Flavors)
75+
### F. Build Variants (Flavors)
6876
- **`google`**: Includes Google Play Services (Maps, Firebase, Crashlytics).
6977
- **`fdroid`**: FOSS version. **Strictly segregate sensitive data** (Crashlytics, Firebase, etc.) out of this flavor.
7078
- **Task Example:** `./gradlew assembleFdroidDebug`
@@ -83,7 +91,10 @@ This file serves as a comprehensive guide for AI agents and developers working o
8391
8492
### C. Testing
8593
- **Unit Tests:** JUnit 4/5 in `src/test/java`. Run with `./gradlew test`.
86-
- **UI Tests:** Espresso/Compose in `src/androidTest/java`. Run with `./gradlew connectedAndroidTest`.
94+
- **Compose UI Tests (JVM):** Preferred for component testing. Use **Robolectric** in `src/test/java`.
95+
- **Important:** Annotate with `@Config(sdk = [34])` if using Java 17 to avoid SDK 35 compatibility issues.
96+
- **Best Practice:** Pass mocked ViewModels to Composables instead of using Hilt in Robolectric tests.
97+
- **Instrumented Tests:** For full E2E or Hilt integration tests, use `src/androidTest/java`. Run with `./gradlew connectedAndroidTest`.
8798
- **Feature Test:** `./gradlew feature:settings:testGoogleDebug`
8899
89100
## 5. Agent Workflow

CONTRIBUTING.md

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@ Thank you for your interest in contributing to Meshtastic-Android! We welcome co
1919
- Write clear, descriptive variable and function names.
2020
- Add comments where necessary, especially for complex logic.
2121
- Keep methods and classes focused and concise.
22-
- Use localised strings; edit the English [`strings.xml`](app/src/main/res/values/strings.xml) file. CrowdIn will manage translations to other languages.
23-
- For example,
24-
22+
- **Strings:** Use localised strings via the **Compose Multiplatform Resource** library in `:core:strings`.
23+
- Do **not** use the legacy `app/src/main/res/values/strings.xml`.
24+
- **Definition:** Add strings to `core/strings/src/commonMain/composeResources/values/strings.xml`.
25+
- **Usage:**
2526
```kotlin
26-
// instead of hardcoding a string in your code:
27-
Text("Settings")
27+
import org.jetbrains.compose.resources.stringResource
28+
import org.meshtastic.core.strings.Res
29+
import org.meshtastic.core.strings.your_string_key
2830

29-
// use the localised string resource:
30-
Text(stringResource(R.string.settings))
31+
Text(text = stringResource(Res.string.your_string_key))
3132
```
3233

3334
### Linting
@@ -43,18 +44,20 @@ Consistent linting helps keep the codebase clean and maintainable.
4344

4445
### Testing
4546

46-
Meshtastic-Android uses both unit tests and instrumented UI tests to ensure code quality and reliability.
47+
Meshtastic-Android uses unit tests, Robolectric JVM tests, and instrumented UI tests to ensure code quality and reliability.
4748

48-
- **Unit tests** are located in `app/src/test/java/` and should be written for all new logic where possible.
49-
- **Instrumented tests** (including UI tests using Jetpack Compose) are located in `app/src/androidTest/java/`. For Compose UI, use the [Jetpack Compose Testing APIs](https://developer.android.com/jetpack/compose/testing).
49+
- **Unit tests** are located in the `src/test/` directory of each module.
50+
- **Compose UI Tests (JVM)** are preferred for component testing and are also located in `src/test/` using **Robolectric**.
51+
- Note: If using Java 17, pin your Robolectric tests to `@Config(sdk = [34])` to avoid SDK 35 compatibility issues.
52+
- **Instrumented tests** (including full E2E UI tests) are located in `src/androidTest/`. For Compose UI, use the [Jetpack Compose Testing APIs](https://developer.android.com/jetpack/compose/testing).
5053

5154
#### Guidelines for Testing
5255

5356
- Add or update tests for any new features or bug fixes.
5457
- Ensure all tests pass by running:
55-
- `./gradlew test` for unit tests
58+
- `./gradlew test` for unit and Robolectric tests
5659
- `./gradlew connectedAndroidTest` for instrumented tests
57-
- For UI components, write Compose UI tests to verify user interactions and visual elements. See existing tests in `DebugFiltersTest.kt` for examples.
60+
- For UI components, write Robolectric Compose tests where possible for faster execution.
5861
- If your change is difficult to test, explain why in your pull request.
5962

6063
Comprehensive testing helps prevent regressions and ensures a stable experience for all users.
@@ -70,7 +73,7 @@ Comprehensive testing helps prevent regressions and ensures a stable experience
7073
- reserved (release, automation)
7174
- Ensure your branch is up to date with the latest `main` branch before submitting a PR.
7275
- Provide a meaningful title and description for your PR.
73-
- Inlude information on how to test and/or replicate if it is not obvious.
76+
- Include information on how to test and/or replicate if it is not obvious.
7477
- Include screenshots or logs if your change affects the UI or user experience.
7578
- Be responsive to feedback and make requested changes promptly.
7679
- Squash commits if requested by a maintainer.

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@ You can generate the documentation locally to preview your changes.
5656
2. **View the output:**
5757
The generated HTML files will be located in the `app/build/dokka/html` directory. You can open the `index.html` file in your browser to view the documentation.
5858

59+
## Architecture
60+
61+
### Modern Android Development (MAD)
62+
The app follows modern Android development practices:
63+
- **UI:** Jetpack Compose (Material 3).
64+
- **State Management:** Unidirectional Data Flow (UDF) with ViewModels, Coroutines, and Flow.
65+
- **Dependency Injection:** Hilt.
66+
- **Navigation:** Type-Safe Navigation (Jetpack Navigation).
67+
- **Data Layer:** Repository pattern with Room (local DB), DataStore (prefs), and Protobuf (device comms).
68+
69+
### Bluetooth Low Energy (BLE)
70+
The BLE stack has been modernized to use **Nordic Semiconductor's Android Common Libraries** and **Kotlin BLE Library**. This provides a robust, Coroutine-based architecture for reliable device communication. See [core/ble/README.md](core/ble/README.md) for details.
71+
5972
## Translations
6073
6174
You can help translate the app into your native language using [Crowdin](https://crowdin.meshtastic.org/android).

app/README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,53 @@
11
# `:app`
22

3+
## Overview
4+
The `:app` module is the entry point for the Meshtastic Android application. It orchestrates the various feature modules, manages global state, and provides the main UI shell.
5+
6+
## Key Components
7+
8+
### 1. `MainActivity` & `Main.kt`
9+
The single Activity of the application. It hosts the `NavHost` and manages the root UI structure (Navigation Bar, Rail, etc.).
10+
11+
### 2. `MeshService`
12+
The core background service that manages long-running communication with the mesh radio. It runs as a **Foreground Service** to ensure reliable communication even when the app is in the background.
13+
14+
### 3. Hilt Application
15+
`MeshUtilApplication` is the Hilt entry point, providing the global dependency injection container.
16+
17+
## Architecture
18+
The module primarily serves as a "glue" layer, connecting:
19+
- `core:*` modules for shared logic.
20+
- `feature:*` modules for specific user-facing screens.
21+
322
## Module dependency graph
423

524
<!--region graph-->
625
```mermaid
726
graph TB
8-
:app[app]:::null
27+
:app[app]:::android-application
28+
:app -.-> :core:analytics
29+
:app -.-> :core:ble
30+
:app -.-> :core:common
31+
:app -.-> :core:data
32+
:app -.-> :core:database
33+
:app -.-> :core:datastore
34+
:app -.-> :core:di
35+
:app -.-> :core:model
36+
:app -.-> :core:navigation
37+
:app -.-> :core:network
38+
:app -.-> :core:nfc
39+
:app -.-> :core:prefs
40+
:app -.-> :core:proto
41+
:app -.-> :core:service
42+
:app -.-> :core:strings
43+
:app -.-> :core:ui
44+
:app -.-> :core:barcode
45+
:app -.-> :feature:intro
46+
:app -.-> :feature:messaging
47+
:app -.-> :feature:map
48+
:app -.-> :feature:node
49+
:app -.-> :feature:settings
50+
:app -.-> :feature:firmware
951
1052
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
1153
classDef android-application-compose fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;

app/build.gradle.kts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,10 @@ configure<ApplicationExtension> {
141141
// Configure existing product flavors (defined by convention plugin)
142142
// with their dynamic version names.
143143
productFlavors {
144-
named("google") { versionName = "${defaultConfig.versionName} (${defaultConfig.versionCode}) google" }
144+
named("google") {
145+
versionName = "${defaultConfig.versionName} (${defaultConfig.versionCode}) google"
146+
manifestPlaceholders["MAPS_API_KEY"] = "dummy"
147+
}
145148
named("fdroid") { versionName = "${defaultConfig.versionName} (${defaultConfig.versionCode}) fdroid" }
146149
}
147150

@@ -158,6 +161,8 @@ configure<ApplicationExtension> {
158161
}
159162
}
160163
bundle { language { enableSplit = false } }
164+
165+
testOptions { unitTests { isIncludeAndroidResources = true } }
161166
}
162167

163168
secrets {
@@ -195,6 +200,7 @@ project.afterEvaluate {
195200

196201
dependencies {
197202
implementation(projects.core.analytics)
203+
implementation(projects.core.ble)
198204
implementation(projects.core.common)
199205
implementation(projects.core.data)
200206
implementation(projects.core.database)
@@ -225,9 +231,7 @@ dependencies {
225231
implementation(libs.androidx.compose.material3)
226232
implementation(libs.androidx.compose.material.iconsExtended)
227233
implementation(libs.androidx.compose.ui.tooling.preview)
228-
implementation(libs.androidx.compose.runtime.livedata)
229234
implementation(libs.androidx.compose.ui.text)
230-
implementation(libs.androidx.lifecycle.livedata.ktx)
231235
implementation(libs.androidx.lifecycle.process)
232236
implementation(libs.androidx.lifecycle.viewmodel.compose)
233237
implementation(libs.androidx.lifecycle.runtime.compose)
@@ -246,6 +250,11 @@ dependencies {
246250
implementation(libs.kermit)
247251

248252
implementation(libs.nordic.client.android)
253+
implementation(libs.nordic.common.core)
254+
implementation(libs.nordic.common.permissions.ble)
255+
implementation(libs.nordic.common.permissions.notification)
256+
implementation(libs.nordic.common.scanner.ble)
257+
implementation(libs.nordic.common.ui)
249258

250259
debugImplementation(libs.androidx.compose.ui.test.manifest)
251260

@@ -259,16 +268,20 @@ dependencies {
259268
androidTestImplementation(libs.androidx.test.ext.junit)
260269
androidTestImplementation(libs.hilt.android.testing)
261270
androidTestImplementation(libs.kotlinx.coroutines.test)
271+
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
272+
androidTestImplementation(libs.nordic.client.android.mock)
273+
androidTestImplementation(libs.nordic.core.mock)
262274

263275
testImplementation(libs.junit)
264276
testImplementation(libs.mockk)
265277
testImplementation(libs.kotlinx.coroutines.test)
266278
testImplementation(libs.nordic.client.android.mock)
267-
testImplementation(libs.nordic.client.mock)
279+
testImplementation(libs.nordic.client.core.mock)
268280
testImplementation(libs.nordic.core.mock)
269-
testImplementation(libs.nordic.core.android.mock)
270281
testImplementation(libs.robolectric)
271282
testImplementation(libs.androidx.test.core)
283+
testImplementation(libs.androidx.compose.ui.test.junit4)
284+
testImplementation(libs.androidx.test.ext.junit)
272285
}
273286

274287
aboutLibraries {

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@
112112
android:hardwareAccelerated="true"
113113
android:theme="@style/SplashTheme"
114114
android:localeConfig="@xml/locales_config"
115-
android:networkSecurityConfig="@xml/network_security_config">
115+
android:networkSecurityConfig="@xml/network_security_config"
116+
android:enableOnBackInvokedCallback="true">
116117

117118
<uses-library
118119
android:name="org.apache.http.legacy"

0 commit comments

Comments
 (0)