From e00b4094d6146220caa30e4e6b2c18e102d8597e Mon Sep 17 00:00:00 2001 From: halaprix Date: Fri, 3 Jul 2026 10:39:36 +0200 Subject: [PATCH 1/3] fix(phone): use hms wear engine receiver Replace the phone-side placeholder receiver with Huawei HiWear/P2pClient APIs. Wire receiver startup and shutdown to the activity lifecycle, and document local peer fingerprint configuration. Local verification: privacy scan and git diff check pass. Android compile is left to CI because this host lacks Java and Gradle. Refs: lw-ehz --- .beads/issues.jsonl | 2 +- CHANGELOG.md | 10 +- README.md | 2 +- docs/AGC_SETUP.md | 4 +- docs/E2E_TESTING.md | 42 +++-- phone/app/build.gradle.kts | 23 +++ .../com/halaprix/leakwatch/MainActivity.kt | 15 +- .../leakwatch/p2p/WearEngineReceiver.kt | 163 +++++++++++++++++- 8 files changed, 220 insertions(+), 41 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index d547aef..35f7c6c 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,4 +1,4 @@ -{"_type":"issue","id":"lw-ehz","title":"review: replace fake Wear OS Data Layer usage with real HMS Wear Engine P2P","description":"Consolidates agy/Claude review findings: v0.4.0-alpha.1 claims real HMS P2P, but phone/watch code appears shaped like Google Wear OS Data Layer, not HMS Wear Engine. Fix approach: verify current Huawei SDK APIs, replace phone receiver/sender patterns with HiWear/P2pClient-style HMS APIs, align registration/lifecycle, and update docs/status if blocked by AGC/device verification.","status":"open","priority":0,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:24:11Z","created_by":"halaprix","updated_at":"2026-07-02T19:24:11Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"lw-ehz","title":"review: replace fake Wear OS Data Layer usage with real HMS Wear Engine P2P","description":"Consolidates agy/Claude review findings: v0.4.0-alpha.1 claims real HMS P2P, but phone/watch code appears shaped like Google Wear OS Data Layer, not HMS Wear Engine. Fix approach: verify current Huawei SDK APIs, replace phone receiver/sender patterns with HiWear/P2pClient-style HMS APIs, align registration/lifecycle, and update docs/status if blocked by AGC/device verification.","status":"in_progress","priority":0,"issue_type":"bug","assignee":"halaprix","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:24:11Z","created_by":"halaprix","updated_at":"2026-07-03T07:51:35Z","started_at":"2026-07-03T07:51:35Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-7po","title":"review: fix phone compile blockers in mock data and chart code","description":"Consolidates agy/Claude review findings: LeakWatchViewModel.insertMockBatch constructs BatteryReading with wrong/missing fields and an Int voltage where Long is required; BatteryDrainChart imports/API do not match declared Vico dependency. Fix approach: align mock data with BatteryReading schema, import missing Compose symbols, and either pin Vico 2.x or rewrite chart to declared Vico API.","status":"closed","priority":0,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:24:10Z","created_by":"halaprix","updated_at":"2026-07-02T19:47:03Z","started_at":"2026-07-02T19:24:56Z","closed_at":"2026-07-02T19:47:03Z","close_reason":"Phone compile blockers resolved; required GitHub Actions phone build passed on 2fea83e after fixing Compose compiler plugin, ignored Android resources, BuildConfig, and non-compiling Wear OS receiver code.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-ccj","title":"review: restore meaningful CI gates for phone/watch builds","description":"Consolidates agy/Claude review findings: CI currently gives false confidence. Phone build is skipped behind a missing phone/gradlew guard; watch build only checks structure; all-green does not require real app compile/test. Fix approach: add or wire a real Android/Gradle build path, make phone build unconditional once module exists, keep watch limitations explicit, and require meaningful gates before release.","status":"closed","priority":0,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:24:09Z","created_by":"halaprix","updated_at":"2026-07-02T19:47:02Z","started_at":"2026-07-02T19:26:00Z","closed_at":"2026-07-02T19:47:02Z","close_reason":"Required CI app gates restored; GitHub Actions CI passed on 2fea83e with Hygiene, Watch module, Phone module, Docs, and All checks successful.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-ugp","title":"ci: fix failing CodeQL workflow","description":"Discovered while verifying review-finding work on 2026-07-02: the main CI workflow is now passing, but the separate CodeQL workflow still fails on main. Inspect the CodeQL run logs, decide whether Kotlin/Android analysis is correctly configured for this repo, and either make CodeQL meaningful or disable only the broken/inert part with documentation.","status":"open","priority":1,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:50:00Z","created_by":"halaprix","updated_at":"2026-07-02T19:50:00Z","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/CHANGELOG.md b/CHANGELOG.md index d51f89e..f17c8ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,16 +12,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- **v0.4.0-alpha.1** — Real HMS Wear Engine P2P (end-to-end) +- **v0.4.0-alpha.1** — Real HMS Wear Engine P2P receiver/sender implementation (device E2E pending) - `phone/app/src/main/java/com/halaprix/leakwatch/p2p/BatteryReadingSerializer.kt` — JSON serialization for P2P - - `phone/app/src/main/java/com/halaprix/leakwatch/p2p/WearEngineReceiver.kt` — real Wear Engine DataClient receiver - - `watch/entry/src/main/ets/service/WearEngineSender.ets` — real Wear Engine DataClient sender (replaces stub) + - `phone/app/src/main/java/com/halaprix/leakwatch/p2p/WearEngineReceiver.kt` — HMS `HiWear`/`P2pClient` receiver and Room ingest + - `watch/entry/src/main/ets/service/WearEngineSender.ets` — watch-side Wear Engine sender candidate (requires device verification) - `docs/E2E_TESTING.md` — end-to-end testing guide with AGC setup - - HMS Wear Engine 5.0.1.300 dependency (both watch and phone) + - HMS Wear Engine 5.0.1.300 dependency - HMS Maven repository in phone/build.gradle.kts - - AndroidManifest.xml: WearEngineReceiverService declaration with intent filter - P2P data format: JSON array of BatteryReading objects - - Path: `/leakwatch/battery`, Key: `battery_readings` - **v0.2.0-alpha.1** — Phone-side data layer + daily aggregation - `phone/app/src/main/java/com/halaprix/leakwatch/data/DailySummary.kt` — daily battery summary entity - `phone/app/src/main/java/com/halaprix/leakwatch/data/DailySummaryDao.kt` — DAO for daily summaries diff --git a/README.md b/README.md index 03df3c3..c974a3a 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Full architecture: [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md). - [x] `v0.1.0-alpha` — Watch-side `BatteryMonitor` sampling loop (ArkTS) + RDB - [x] `v0.2.0-alpha` — Phone-side Wear Engine receiver + Room persistence - [x] `v0.3.0-alpha` — Vico chart, daily aggregation, drain rate -- [x] `v0.4.0-alpha` — Real HMS Wear Engine P2P (v0.4.0-alpha.1 tagged) +- [x] `v0.4.0-alpha` — HMS Wear Engine receiver/sender implementation (v0.4.0-alpha.1 tagged; device E2E pending) - [ ] `v0.5.0-alpha` — Privacy policy + AppGallery submission - [ ] `v1.0.0` — First public release diff --git a/docs/AGC_SETUP.md b/docs/AGC_SETUP.md index e646485..1f3a059 100644 --- a/docs/AGC_SETUP.md +++ b/docs/AGC_SETUP.md @@ -59,8 +59,8 @@ For Wear Engine specifically: 1. Apply → provide use-case description ("battery telemetry P2P from companion wearable to phone") 2. Wait for Huawei approval 3. Once approved, generate the **fingerprint** in the Wear Engine console -4. Save the fingerprint locally: `local.properties` entry `peerFingerprint=...` -5. Save the package name: `local.properties` entry `peerPkgName=com.halaprix.leakwatch` +4. Save the fingerprint locally: `phone/local.properties` entry `leakwatch.peerFingerprint=...` +5. Save the watch package name locally: `phone/local.properties` entry `leakwatch.watchPackageName=com.halaprix.leakwatch` ### 5. Download `agconnect-services.json` diff --git a/docs/E2E_TESTING.md b/docs/E2E_TESTING.md index 22fd88e..537afc0 100644 --- a/docs/E2E_TESTING.md +++ b/docs/E2E_TESTING.md @@ -6,7 +6,7 @@ v0.4.0-alpha.1 implements **real HMS Wear Engine P2P** communication: - **Watch** (HarmonyOS NEXT): `BatteryMonitor` → `WearEngineSender` → P2P → phone -- **Phone** (Android/HMS): `WearEngineReceiverService` → Room DB → UI charts +- **Phone** (Android/HMS): activity-lifecycle `WearEngineReceiver` → Room DB → UI charts ## Quick Start (If AGC Already Set Up) @@ -34,8 +34,8 @@ cd phone/ # (Download from AGC → Project Settings) # Build → Run on device -# Expected: WearEngineReceiverService starts automatically -# When watch sends data: logs "Received X readings from watch" +# Expected: MainActivity registers WearEngineReceiver through HMS HiWear/P2pClient +# When watch sends data: logs "Inserted X readings from watch" # UI shows: Today tab → live battery, History tab → chart, Settings → analytics ``` @@ -52,8 +52,8 @@ WearEngineSender: Sent 5 records to phone via P2P **Phone logcat:** ``` LeakWatchApp: LeakWatch Application starting -WearEngineReceiverService: WearEngineReceiverService created -WearEngineReceiverService: Received 5 readings from watch +WearEngineReceiver: HMS Wear Engine bonded devices visible: 1 +WearEngineReceiver: Registered HMS Wear Engine receiver for bonded wearable WearEngineReceiver: Inserted 5 readings from watch ``` @@ -97,7 +97,7 @@ See `docs/AGC_SETUP.md` for the full checklist. Quick version: Once Wear Engine is approved: 1. AGC → Wear Engine console → **Generate fingerprint** -2. Save the fingerprint locally (e.g., in `local.properties` as `peerFingerprint=...`) +2. Save the fingerprint locally (for phone builds: `phone/local.properties` entry `leakwatch.peerFingerprint=...`) 3. **Do NOT commit this file** (it's in `.gitignore`) ### Step 5: Download agconnect-services.json @@ -135,9 +135,9 @@ cd phone/ | `HMS Core not initialized` | Wrong path | Check `build-profile.json5` (watch) or `build.gradle.kts` (phone) | | Wear Engine P2P send fails with `DEVICE_OFFLINE` | Watch and phone not paired | Pair them in Huawei Health app, retry | | `peerFingerprint mismatch` | Fingerprint in AGC ≠ local | Regenerate fingerprint in Wear Engine console | -| Phone doesn't receive data | WearEngineReceiverService not running | Check AndroidManifest.xml, ensure service is declared | +| Phone doesn't receive data | Receiver not registered or permission/device query failed | Check logcat for `WearEngineReceiver`, HMS permission, bonded device count, and peer fingerprint config | | Watch logs "DataClient not initialized" | Wear Engine Kit not approved | Wait for Huawei approval (1-2 days) | -| Phone logs "battery_readings key missing" | Watch sending wrong format | Check WearEngineSender.ets serialization | +| Phone logs JSON decode failure | Watch sending wrong format | Check WearEngineSender serialization matches the JSON array documented below | --- @@ -146,19 +146,17 @@ cd phone/ **Watch → Phone P2P payload:** ```json -{ - "battery_readings": [ - { - "ts": 1718524800000, - "level": 85, - "pluggedType": 0, - "chargingStatus": 2, - "voltage": 4100000, - "temperature": 250, - "isPresent": true - } - ] -} +[ + { + "ts": 1718524800000, + "level": 85, + "pluggedType": 0, + "chargingStatus": 2, + "voltage": 4100000, + "temperature": 250, + "isPresent": true + } +] ``` **Phone deserialization:** `BatteryReadingSerializer.deserialize(ByteArray)` → `List` @@ -173,7 +171,7 @@ cd phone/ - Total: **<0.5%/day** (target met) **Phone:** -- WearEngineReceiverService: listener-driven, no polling → negligible +- WearEngineReceiver: listener-driven, no polling → negligible - DailyAggregationWorker: once/day at midnight → negligible - Total: **<0.1%/day** diff --git a/phone/app/build.gradle.kts b/phone/app/build.gradle.kts index d2c8dd1..654ff4a 100644 --- a/phone/app/build.gradle.kts +++ b/phone/app/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { id("com.android.application") id("org.jetbrains.kotlin.android") @@ -5,6 +7,16 @@ plugins { id("com.google.devtools.ksp") } +val localProperties = Properties().apply { + val file = rootProject.file("local.properties") + if (file.exists()) { + file.inputStream().use(::load) + } +} + +fun leakWatchProperty(name: String, default: String = ""): String = + providers.gradleProperty(name).orElse(localProperties.getProperty(name, default)).get() + android { namespace = "com.halaprix.leakwatch" compileSdk = 34 @@ -16,6 +28,17 @@ android { versionCode = 4 versionName = "0.4.0-alpha.1" + buildConfigField( + "String", + "LEAKWATCH_WATCH_PACKAGE_NAME", + "\"${leakWatchProperty("leakwatch.watchPackageName", "com.halaprix.leakwatch")}\"" + ) + buildConfigField( + "String", + "LEAKWATCH_WATCH_PEER_FINGERPRINT", + "\"${leakWatchProperty("leakwatch.peerFingerprint")}\"" + ) + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true diff --git a/phone/app/src/main/java/com/halaprix/leakwatch/MainActivity.kt b/phone/app/src/main/java/com/halaprix/leakwatch/MainActivity.kt index e5c3439..2926411 100644 --- a/phone/app/src/main/java/com/halaprix/leakwatch/MainActivity.kt +++ b/phone/app/src/main/java/com/halaprix/leakwatch/MainActivity.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Surface import androidx.compose.material3.MaterialTheme import androidx.compose.ui.Modifier +import androidx.lifecycle.lifecycleScope import com.halaprix.leakwatch.p2p.WearEngineReceiver import com.halaprix.leakwatch.ui.LeakWatchScreen import com.halaprix.leakwatch.ui.LeakWatchViewModel @@ -18,6 +19,7 @@ import com.halaprix.leakwatch.ui.theme.LeakWatchTheme class MainActivity : ComponentActivity() { private val TAG = "MainActivity" private val viewModel: LeakWatchViewModel by viewModels() + private lateinit var wearEngineReceiver: WearEngineReceiver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -33,7 +35,16 @@ class MainActivity : ComponentActivity() { } } } - - Log.i(TAG, "LeakWatch started — WearEngineReceiverService will receive P2P data from watch") + + wearEngineReceiver = WearEngineReceiver(activity = this, scope = lifecycleScope) + wearEngineReceiver.start() + Log.i(TAG, "LeakWatch started — HMS Wear Engine receiver registered from activity lifecycle") + } + + override fun onDestroy() { + if (::wearEngineReceiver.isInitialized) { + wearEngineReceiver.stop() + } + super.onDestroy() } } diff --git a/phone/app/src/main/java/com/halaprix/leakwatch/p2p/WearEngineReceiver.kt b/phone/app/src/main/java/com/halaprix/leakwatch/p2p/WearEngineReceiver.kt index 00fdd4a..dc5d4c0 100644 --- a/phone/app/src/main/java/com/halaprix/leakwatch/p2p/WearEngineReceiver.kt +++ b/phone/app/src/main/java/com/halaprix/leakwatch/p2p/WearEngineReceiver.kt @@ -1,28 +1,177 @@ package com.halaprix.leakwatch.p2p +import android.app.Activity import android.content.Context import android.util.Log +import com.halaprix.leakwatch.BuildConfig import com.halaprix.leakwatch.data.AppDatabase import com.halaprix.leakwatch.data.BatteryReading +import com.huawei.wearengine.HiWear +import com.huawei.wearengine.auth.AuthCallback +import com.huawei.wearengine.auth.Permission +import com.huawei.wearengine.device.Device +import com.huawei.wearengine.device.DeviceClient +import com.huawei.wearengine.p2p.Message +import com.huawei.wearengine.p2p.P2pClient +import com.huawei.wearengine.p2p.Receiver +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch /** - * Phone-side ingest helper for watch battery readings. + * Phone-side HMS Wear Engine receiver and ingest helper. * - * The previous implementation extended Google Wear OS Data Layer shapes - * (`WearableListenerService`, `DataEvent`, `DataMapItem`) that are not present - * in Huawei's HMS Wear Engine SDK. Keep this class compile-safe and limited to - * database ingest until the real HMS P2P receiver is implemented. + * This uses Huawei's real Wear Engine API shape: + * - [HiWear.getP2pClient] / [P2pClient] + * - [P2pClient.setPeerPkgName] + * - [P2pClient.setPeerFingerPrint] + * - [P2pClient.registerReceiver] + * + * It intentionally does not extend Google Wear OS Data Layer classes + * (`WearableListenerService`, `DataEvent`, `DataMapItem`). */ -class WearEngineReceiver(context: Context) { +class WearEngineReceiver( + private val activity: Activity, + private val scope: CoroutineScope +) { private val tag = "WearEngineReceiver" - private val db = AppDatabase.getDatabase(context) + private val appContext: Context = activity.applicationContext + private val db = AppDatabase.getDatabase(appContext) private val dao = db.batteryReadingDao() + private val p2pClient: P2pClient = HiWear.getP2pClient(activity) + private val deviceClient: DeviceClient = HiWear.getDeviceClient(activity) + private var registeredDevice: Device? = null + + private val receiver = object : Receiver { + override fun onReceiveMessage(message: Message?) { + if (message == null) { + Log.w(tag, "Received null HMS Wear Engine message") + return + } + + val payload = message.data + if (payload == null || payload.isEmpty()) { + Log.w(tag, "Received empty HMS Wear Engine payload") + return + } + + scope.launch { + ingestPayload(payload) + } + } + } + companion object { const val KEY_BATTERY_READINGS = "battery_readings" const val PATH_BATTERY_DATA = "/leakwatch/battery" } + /** + * Initialize HMS P2P receiver for the first bonded Huawei wearable. + * + * Runtime success still depends on Huawei Health pairing, HMS Core, AGC Wear + * Engine approval, matching package name, and peer fingerprint configured via + * local Gradle properties. Missing fingerprint is logged as a probe blocker + * instead of being hard-coded or committed. + */ + fun start() { + configurePeerIdentity() + requestDeviceManagerPermission() + registerOnFirstBondedDevice() + } + + fun stop() { + val device = registeredDevice + if (device == null) { + Log.d(tag, "No HMS receiver registered, stop skipped") + return + } + + p2pClient.unregisterReceiver(receiver) + .addOnSuccessListener { + Log.i(tag, "Unregistered HMS Wear Engine receiver") + } + .addOnFailureListener { error -> + Log.w(tag, "Failed to unregister HMS receiver", error) + } + registeredDevice = null + } + + private fun configurePeerIdentity() { + val watchPackageName = BuildConfig.LEAKWATCH_WATCH_PACKAGE_NAME + val peerFingerprint = BuildConfig.LEAKWATCH_WATCH_PEER_FINGERPRINT + + p2pClient.setPeerPkgName(watchPackageName) + + if (peerFingerprint.isBlank()) { + Log.w( + tag, + "HMS peer fingerprint is blank. Set leakwatch.peerFingerprint in phone/local.properties " + + "after AGC Wear Engine approval; P2P ping/register may fail until then." + ) + } + + p2pClient.setPeerFingerPrint(peerFingerprint) + Log.i(tag, "Configured HMS Wear Engine peer package=$watchPackageName") + } + + private fun requestDeviceManagerPermission() { + val callback = object : AuthCallback { + override fun onOk(permissions: Array) { + Log.i(tag, "HMS Wear Engine permission granted: ${permissions.joinToString()}") + } + + override fun onCancel() { + Log.w(tag, "HMS Wear Engine permission request cancelled") + } + } + + HiWear.getAuthClient(activity) + .requestPermission(callback, Permission.DEVICE_MANAGER) + .addOnSuccessListener { + Log.i(tag, "Submitted HMS Wear Engine DEVICE_MANAGER permission request") + } + .addOnFailureListener { error -> + Log.w(tag, "Failed to request HMS Wear Engine DEVICE_MANAGER permission", error) + } + } + + private fun registerOnFirstBondedDevice() { + deviceClient.getBondedDevices() + .addOnSuccessListener { devices -> + if (devices.isNullOrEmpty()) { + Log.w(tag, "No Huawei Health bonded devices visible to HMS Wear Engine") + return@addOnSuccessListener + } + + Log.i(tag, "HMS Wear Engine bonded devices visible: ${devices.size}") + registerReceiver(devices.first()) + } + .addOnFailureListener { error -> + Log.w(tag, "Failed to query HMS Wear Engine bonded devices", error) + } + } + + private fun registerReceiver(device: Device) { + p2pClient.registerReceiver(device, receiver) + .addOnSuccessListener { + registeredDevice = device + Log.i(tag, "Registered HMS Wear Engine receiver for bonded wearable") + } + .addOnFailureListener { error -> + Log.w(tag, "Failed to register HMS Wear Engine receiver", error) + } + } + + private suspend fun ingestPayload(payload: ByteArray) { + try { + val readings = BatteryReadingSerializer.deserialize(payload) + insertReadings(readings) + } catch (error: Exception) { + Log.w(tag, "Failed to decode HMS Wear Engine battery payload", error) + } + } + /** * Insert a batch of readings received from a transport implementation. */ From 2e6265ec4b0c884232f7c62ac92e382ce3803297 Mon Sep 17 00:00:00 2001 From: halaprix Date: Fri, 3 Jul 2026 10:44:42 +0200 Subject: [PATCH 2/3] chore(beads): track watch p2p verification Record the remaining watch-side HMS Wear Engine device verification. Refs: lw-ehz --- .beads/issues.jsonl | 1 + 1 file changed, 1 insertion(+) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 35f7c6c..cc92f1a 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,3 +1,4 @@ +{"_type":"issue","id":"lw-x6i","title":"review: verify Watch Fit 5 Pro Wear Engine sender API","description":"Discovered while implementing lw-ehz: phone-side receiver can be compiled against Huawei HiWear/P2pClient, but watch-side HarmonyOS 6.1 / Watch Fit 5 Pro sender still needs real device/API verification. Acceptance: confirm the correct watch-side Wear Engine Kit API for HarmonyOS 6.1 ArkTS or Java ability, replace any invented DataClient-style sender code if needed, send one JSON-array battery batch to the phone receiver, and record sanitized log evidence without AGC fingerprints or private paths. Target devices: Huawei Watch Fit 5 Pro on HarmonyOS 6.1.0.110 and Pixel 9 Pro on Android 16.","status":"open","priority":0,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-03T08:44:41Z","created_by":"halaprix","updated_at":"2026-07-03T08:44:41Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-ehz","title":"review: replace fake Wear OS Data Layer usage with real HMS Wear Engine P2P","description":"Consolidates agy/Claude review findings: v0.4.0-alpha.1 claims real HMS P2P, but phone/watch code appears shaped like Google Wear OS Data Layer, not HMS Wear Engine. Fix approach: verify current Huawei SDK APIs, replace phone receiver/sender patterns with HiWear/P2pClient-style HMS APIs, align registration/lifecycle, and update docs/status if blocked by AGC/device verification.","status":"in_progress","priority":0,"issue_type":"bug","assignee":"halaprix","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:24:11Z","created_by":"halaprix","updated_at":"2026-07-03T07:51:35Z","started_at":"2026-07-03T07:51:35Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-7po","title":"review: fix phone compile blockers in mock data and chart code","description":"Consolidates agy/Claude review findings: LeakWatchViewModel.insertMockBatch constructs BatteryReading with wrong/missing fields and an Int voltage where Long is required; BatteryDrainChart imports/API do not match declared Vico dependency. Fix approach: align mock data with BatteryReading schema, import missing Compose symbols, and either pin Vico 2.x or rewrite chart to declared Vico API.","status":"closed","priority":0,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:24:10Z","created_by":"halaprix","updated_at":"2026-07-02T19:47:03Z","started_at":"2026-07-02T19:24:56Z","closed_at":"2026-07-02T19:47:03Z","close_reason":"Phone compile blockers resolved; required GitHub Actions phone build passed on 2fea83e after fixing Compose compiler plugin, ignored Android resources, BuildConfig, and non-compiling Wear OS receiver code.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-ccj","title":"review: restore meaningful CI gates for phone/watch builds","description":"Consolidates agy/Claude review findings: CI currently gives false confidence. Phone build is skipped behind a missing phone/gradlew guard; watch build only checks structure; all-green does not require real app compile/test. Fix approach: add or wire a real Android/Gradle build path, make phone build unconditional once module exists, keep watch limitations explicit, and require meaningful gates before release.","status":"closed","priority":0,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:24:09Z","created_by":"halaprix","updated_at":"2026-07-02T19:47:02Z","started_at":"2026-07-02T19:26:00Z","closed_at":"2026-07-02T19:47:02Z","close_reason":"Required CI app gates restored; GitHub Actions CI passed on 2fea83e with Hygiene, Watch module, Phone module, Docs, and All checks successful.","dependency_count":0,"dependent_count":0,"comment_count":0} From b0857079f0d91e760258aa7a4848f80d08d0239f Mon Sep 17 00:00:00 2001 From: halaprix Date: Fri, 3 Jul 2026 10:54:07 +0200 Subject: [PATCH 3/3] fix(phone): wait for hms permission Sequence receiver registration after DEVICE_MANAGER permission is granted. Also tighten public docs so P2P/device E2E and battery numbers are clearly marked as pending verification. Refs: lw-cus, lw-871, lw-ehz --- .beads/interactions.jsonl | 2 + .beads/issues.jsonl | 2 + CHANGELOG.md | 4 +- README.md | 24 ++++---- docs/ARCHITECTURE.md | 2 +- docs/E2E_TESTING.md | 18 +++--- .../leakwatch/p2p/WearEngineReceiver.kt | 57 ++++++++++++++++++- 7 files changed, 85 insertions(+), 24 deletions(-) diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl index ff56ee7..bad5406 100644 --- a/.beads/interactions.jsonl +++ b/.beads/interactions.jsonl @@ -12,3 +12,5 @@ {"id":"int-bbc30961","kind":"field_change","created_at":"2026-07-02T19:34:04.04102542Z","actor":"halaprix","issue_id":"lw-7po","extra":{"field":"status","new_value":"in_progress","old_value":"open"}} {"id":"int-a29e9acd","kind":"field_change","created_at":"2026-07-02T19:47:02.427584921Z","actor":"halaprix","issue_id":"lw-ccj","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Required CI app gates restored; GitHub Actions CI passed on 2fea83e with Hygiene, Watch module, Phone module, Docs, and All checks successful."}} {"id":"int-a24c4ab1","kind":"field_change","created_at":"2026-07-02T19:47:03.178564521Z","actor":"halaprix","issue_id":"lw-7po","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Phone compile blockers resolved; required GitHub Actions phone build passed on 2fea83e after fixing Compose compiler plugin, ignored Android resources, BuildConfig, and non-compiling Wear OS receiver code."}} +{"id":"int-c7d14143","kind":"field_change","created_at":"2026-07-03T08:54:06.649804938Z","actor":"halaprix","issue_id":"lw-cus","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Fixed in PR #27 by sequencing HMS receiver registration after DEVICE_MANAGER permission grant."}} +{"id":"int-5962c432","kind":"field_change","created_at":"2026-07-03T08:54:07.244399223Z","actor":"halaprix","issue_id":"lw-871","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Fixed in PR #27 by removing overconfident README/E2E/architecture/changelog claims."}} diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index cc92f1a..24855cd 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,7 +1,9 @@ +{"_type":"issue","id":"lw-cus","title":"review: sequence HMS receiver registration after permission","description":"Subagent review found WearEngineReceiver.start() requests DEVICE_MANAGER permission and immediately queries/registers devices. Huawei Wear Engine permission is async and covers device/message access, so first install can fail without retry. Acceptance: register only after permission is granted or already granted, and avoid late async registration after stop().","status":"closed","priority":0,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-03T08:51:58Z","created_by":"halaprix","updated_at":"2026-07-03T08:54:07Z","closed_at":"2026-07-03T08:54:07Z","close_reason":"Fixed in PR #27 by sequencing HMS receiver registration after DEVICE_MANAGER permission grant.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-x6i","title":"review: verify Watch Fit 5 Pro Wear Engine sender API","description":"Discovered while implementing lw-ehz: phone-side receiver can be compiled against Huawei HiWear/P2pClient, but watch-side HarmonyOS 6.1 / Watch Fit 5 Pro sender still needs real device/API verification. Acceptance: confirm the correct watch-side Wear Engine Kit API for HarmonyOS 6.1 ArkTS or Java ability, replace any invented DataClient-style sender code if needed, send one JSON-array battery batch to the phone receiver, and record sanitized log evidence without AGC fingerprints or private paths. Target devices: Huawei Watch Fit 5 Pro on HarmonyOS 6.1.0.110 and Pixel 9 Pro on Android 16.","status":"open","priority":0,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-03T08:44:41Z","created_by":"halaprix","updated_at":"2026-07-03T08:44:41Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-ehz","title":"review: replace fake Wear OS Data Layer usage with real HMS Wear Engine P2P","description":"Consolidates agy/Claude review findings: v0.4.0-alpha.1 claims real HMS P2P, but phone/watch code appears shaped like Google Wear OS Data Layer, not HMS Wear Engine. Fix approach: verify current Huawei SDK APIs, replace phone receiver/sender patterns with HiWear/P2pClient-style HMS APIs, align registration/lifecycle, and update docs/status if blocked by AGC/device verification.","status":"in_progress","priority":0,"issue_type":"bug","assignee":"halaprix","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:24:11Z","created_by":"halaprix","updated_at":"2026-07-03T07:51:35Z","started_at":"2026-07-03T07:51:35Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-7po","title":"review: fix phone compile blockers in mock data and chart code","description":"Consolidates agy/Claude review findings: LeakWatchViewModel.insertMockBatch constructs BatteryReading with wrong/missing fields and an Int voltage where Long is required; BatteryDrainChart imports/API do not match declared Vico dependency. Fix approach: align mock data with BatteryReading schema, import missing Compose symbols, and either pin Vico 2.x or rewrite chart to declared Vico API.","status":"closed","priority":0,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:24:10Z","created_by":"halaprix","updated_at":"2026-07-02T19:47:03Z","started_at":"2026-07-02T19:24:56Z","closed_at":"2026-07-02T19:47:03Z","close_reason":"Phone compile blockers resolved; required GitHub Actions phone build passed on 2fea83e after fixing Compose compiler plugin, ignored Android resources, BuildConfig, and non-compiling Wear OS receiver code.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-ccj","title":"review: restore meaningful CI gates for phone/watch builds","description":"Consolidates agy/Claude review findings: CI currently gives false confidence. Phone build is skipped behind a missing phone/gradlew guard; watch build only checks structure; all-green does not require real app compile/test. Fix approach: add or wire a real Android/Gradle build path, make phone build unconditional once module exists, keep watch limitations explicit, and require meaningful gates before release.","status":"closed","priority":0,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:24:09Z","created_by":"halaprix","updated_at":"2026-07-02T19:47:02Z","started_at":"2026-07-02T19:26:00Z","closed_at":"2026-07-02T19:47:02Z","close_reason":"Required CI app gates restored; GitHub Actions CI passed on 2fea83e with Hygiene, Watch module, Phone module, Docs, and All checks successful.","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"lw-871","title":"review: remove overconfident LeakWatch P2P docs","description":"Subagent review found README/E2E docs still overstate Vico, scaffold status, battery-budget measurements, and E2E P2P despite device testing and watch sender verification being pending. Acceptance: public docs clearly state phone receiver implementation is compiled, device E2E/watch sender verification remains pending, chart currently uses Compose Canvas, and battery figures are targets until measured.","status":"closed","priority":1,"issue_type":"task","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-03T08:51:59Z","created_by":"halaprix","updated_at":"2026-07-03T08:54:07Z","closed_at":"2026-07-03T08:54:07Z","close_reason":"Fixed in PR #27 by removing overconfident README/E2E/architecture/changelog claims.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-ugp","title":"ci: fix failing CodeQL workflow","description":"Discovered while verifying review-finding work on 2026-07-02: the main CI workflow is now passing, but the separate CodeQL workflow still fails on main. Inspect the CodeQL run logs, decide whether Kotlin/Android analysis is correctly configured for this repo, and either make CodeQL meaningful or disable only the broken/inert part with documentation.","status":"open","priority":1,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:50:00Z","created_by":"halaprix","updated_at":"2026-07-02T19:50:00Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-76i","title":"review: tighten privacy scanner for file-path secrets and AGC fingerprints","description":"Consolidates Claude review findings: privacy scanner checks keystore extensions via content grep instead of tracked filenames, Tailscale pattern is too narrow, AGC fingerprint detection is missing, and broad allowlists can mask leaks. Fix approach: add filename-based checks for .jks/.keystore/.p12, broaden sensitive hostname patterns, add AGC fingerprint detection, and reassess broad allowlists without disclosing secrets.","status":"open","priority":1,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:24:13Z","created_by":"halaprix","updated_at":"2026-07-02T19:24:13Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"lw-6ne","title":"review: fix watch UI listener leak and batteryInfo listener cooldown","description":"Consolidates agy/Claude review findings: Index.ets removes a new anonymous listener instead of the registered callback, leaking component listeners; native batteryInfoChange path lacks the documented 30s cooldown. Fix approach: store listener reference as a field, deregister it on disappear, and debounce native listener events.","status":"open","priority":1,"issue_type":"bug","owner":"halaprix@users.noreply.github.com","created_at":"2026-07-02T19:24:12Z","created_by":"halaprix","updated_at":"2026-07-02T19:24:12Z","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/CHANGELOG.md b/CHANGELOG.md index f17c8ac..1956391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- **v0.4.0-alpha.1** — Real HMS Wear Engine P2P receiver/sender implementation (device E2E pending) +- **v0.4.0-alpha.1** — Real HMS Wear Engine phone receiver implementation (watch sender/device E2E pending) - `phone/app/src/main/java/com/halaprix/leakwatch/p2p/BatteryReadingSerializer.kt` — JSON serialization for P2P - `phone/app/src/main/java/com/halaprix/leakwatch/p2p/WearEngineReceiver.kt` — HMS `HiWear`/`P2pClient` receiver and Room ingest - - `watch/entry/src/main/ets/service/WearEngineSender.ets` — watch-side Wear Engine sender candidate (requires device verification) + - `watch/entry/src/main/ets/service/WearEngineSender.ets` — watch-side Wear Engine sender candidate (tracked for device/API verification) - `docs/E2E_TESTING.md` — end-to-end testing guide with AGC setup - HMS Wear Engine 5.0.1.300 dependency - HMS Maven repository in phone/build.gradle.kts diff --git a/README.md b/README.md index c974a3a..45d42cd 100644 --- a/README.md +++ b/README.md @@ -52,14 +52,14 @@ The name is a double meaning: **leak** (battery leak) + **watch** (the wrist thi │ BatteryMonitor │ │ WearEngine │ │ + RDB history │ │ Receiver │ │ + Service Ext. │ │ + Room │ -└──────────────────┘ │ + Vico chart │ +└──────────────────┘ │ + Compose chart │ └──────────────────┘ ``` -- **Watch side** samples `batterySOC`, `chargingStatus`, `batteryTemperature`, `voltage` every **120s** (not 60s) and flushes batches every **10min** over HMS Wear Engine P2P. -- **Phone side** persists, aggregates daily, and draws the drain curve with Vico. +- **Watch side** samples `batterySOC`, `chargingStatus`, `batteryTemperature`, `voltage` every **120s** (not 60s) and flushes batches every **10min** over HMS Wear Engine P2P once the watch sender is verified on device. +- **Phone side** persists, aggregates daily, and draws the drain curve with a Compose Canvas chart. - **No foreground service on the watch.** `Service Extension` + work scheduling only. -- **Total target drain: < 0.5%/24h on the watch**, verified in `docs/BATTERY_BUDGET.md`. +- **Total target drain: < 0.5%/24h on the watch**; measured device evidence is still pending in `docs/BATTERY_BUDGET.md`. Full architecture: [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md). @@ -67,12 +67,12 @@ Full architecture: [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md). ## 🚧 Status -**Alpha (v0.4.0).** Watch battery monitor + Wear Engine P2P + phone analytics landed. Next milestones: +**Alpha (v0.4.0).** Watch battery monitor, phone analytics, and the phone-side HMS receiver are in place; real watch→phone E2E remains pending device verification. Next milestones: - [x] `v0.1.0-alpha` — Watch-side `BatteryMonitor` sampling loop (ArkTS) + RDB - [x] `v0.2.0-alpha` — Phone-side Wear Engine receiver + Room persistence -- [x] `v0.3.0-alpha` — Vico chart, daily aggregation, drain rate -- [x] `v0.4.0-alpha` — HMS Wear Engine receiver/sender implementation (v0.4.0-alpha.1 tagged; device E2E pending) +- [x] `v0.3.0-alpha` — Compose chart, daily aggregation, drain rate +- [x] `v0.4.0-alpha` — HMS Wear Engine phone receiver implementation (v0.4.0-alpha.1 tagged; watch sender/device E2E pending) - [ ] `v0.5.0-alpha` — Privacy policy + AppGallery submission - [ ] `v1.0.0` — First public release @@ -88,7 +88,7 @@ See [`CHANGELOG.md`](./CHANGELOG.md) for what landed when. | Watch ↔ Phone | HMS Wear Engine 5.0.1.300 | P2P, idiomatic, exposes wearable battery | | Phone (Kotlin) | 2.0 + Jetpack Compose Material 3 | Modern, Marian-familiar | | Phone storage | Room 2.6.1 | Reliable, KSP-friendly | -| Phone charts | Vico 2.0 | Compose-native, small footprint | +| Phone charts | Compose Canvas | Buildable lightweight chart; Vico may return later on a pinned version | | HMS analytics | HMS Analytics 6.13 + HMS Crash | Replaces Firebase | | Build (watch) | Hvigor 5.1.0+ | DevEco Studio standard | | Build (phone) | Gradle 8.7 + AGP 8.5 | Standard Android toolchain | @@ -97,7 +97,7 @@ See [`CHANGELOG.md`](./CHANGELOG.md) for what landed when. ## 🏁 Getting Started -> ⚠️ **No app code yet.** The `watch/` and `phone/` modules land in the next milestones. This section will grow then. +> ⚠️ **Alpha code exists.** The `watch/` and `phone/` modules build through CI structure/Android gates, but real device E2E still requires AGC Wear Engine setup and watch-side verification. ### Prerequisites @@ -106,12 +106,14 @@ See [`CHANGELOG.md`](./CHANGELOG.md) for what landed when. - **JDK 17+**, **Node.js 20+** (for tooling) - **A Huawei Developer account** — to apply for HMS Wear Engine Kit in AppGallery Connect -### Local setup (scaffolding only) +### Local setup ```bash git clone https://github.com/halaprix/leakwatch cd leakwatch -# Nothing to build yet. Run `git log` to see the bootstrap. +# Phone module: open phone/ in Android Studio or run Gradle with JDK 17. +# Watch module: open watch/ in DevEco Studio. +# Real P2P requires AGC Wear Engine approval and local, uncommitted config. ``` ### Contributing diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index cb71a74..4284b8f 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -59,7 +59,7 @@ batteryInfo.on('batteryInfoChange') → WearEngine P2P [batched every 10 min] → Phone: WearEngineReceiver → Room (BatteryReading entity) - → Vico chart (Compose UI) + → Compose Canvas chart ``` ## Open questions diff --git a/docs/E2E_TESTING.md b/docs/E2E_TESTING.md index 537afc0..2162b1d 100644 --- a/docs/E2E_TESTING.md +++ b/docs/E2E_TESTING.md @@ -4,7 +4,7 @@ ## Overview -v0.4.0-alpha.1 implements **real HMS Wear Engine P2P** communication: +v0.4.0-alpha.1 contains the phone-side HMS receiver path and a watch-side sender candidate. Full watch→phone device E2E is still pending AGC Wear Engine setup and watch-side API verification. - **Watch** (HarmonyOS NEXT): `BatteryMonitor` → `WearEngineSender` → P2P → phone - **Phone** (Android/HMS): activity-lifecycle `WearEngineReceiver` → Room DB → UI charts @@ -165,21 +165,23 @@ cd phone/ ## Battery Budget +These are design targets until measured on the current watch/phone pair. + **Watch:** -- BatteryMonitor: 120s polling → ~0.3%/day -- WearEngineSender: 10min flush → ~0.1%/day -- Total: **<0.5%/day** (target met) +- BatteryMonitor: 120s polling → target ~0.3%/day +- WearEngineSender: 10min flush → target ~0.1%/day +- Total target: **<0.5%/day** **Phone:** -- WearEngineReceiver: listener-driven, no polling → negligible -- DailyAggregationWorker: once/day at midnight → negligible -- Total: **<0.1%/day** +- WearEngineReceiver: listener-driven, no polling → target negligible +- DailyAggregationWorker: once/day at midnight → target negligible +- Total target: **<0.1%/day** --- ## Next Steps -After successful e2e testing: +After successful device E2E testing: - **v0.5.0-alpha.1**: Low-battery alerts (Push Kit) - **v1.0.0**: AppGallery release candidate diff --git a/phone/app/src/main/java/com/halaprix/leakwatch/p2p/WearEngineReceiver.kt b/phone/app/src/main/java/com/halaprix/leakwatch/p2p/WearEngineReceiver.kt index dc5d4c0..8c0159a 100644 --- a/phone/app/src/main/java/com/halaprix/leakwatch/p2p/WearEngineReceiver.kt +++ b/phone/app/src/main/java/com/halaprix/leakwatch/p2p/WearEngineReceiver.kt @@ -16,6 +16,7 @@ import com.huawei.wearengine.p2p.P2pClient import com.huawei.wearengine.p2p.Receiver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import java.util.concurrent.atomic.AtomicBoolean /** * Phone-side HMS Wear Engine receiver and ingest helper. @@ -41,6 +42,7 @@ class WearEngineReceiver( private val p2pClient: P2pClient = HiWear.getP2pClient(activity) private val deviceClient: DeviceClient = HiWear.getDeviceClient(activity) private var registeredDevice: Device? = null + private val stopped = AtomicBoolean(true) private val receiver = object : Receiver { override fun onReceiveMessage(message: Message?) { @@ -75,12 +77,13 @@ class WearEngineReceiver( * instead of being hard-coded or committed. */ fun start() { + stopped.set(false) configurePeerIdentity() - requestDeviceManagerPermission() - registerOnFirstBondedDevice() + registerAfterDeviceManagerPermission() } fun stop() { + stopped.set(true) val device = registeredDevice if (device == null) { Log.d(tag, "No HMS receiver registered, stop skipped") @@ -115,10 +118,39 @@ class WearEngineReceiver( Log.i(tag, "Configured HMS Wear Engine peer package=$watchPackageName") } + private fun registerAfterDeviceManagerPermission() { + HiWear.getAuthClient(activity) + .checkPermission(Permission.DEVICE_MANAGER) + .addOnSuccessListener { granted -> + if (stopped.get()) { + Log.d(tag, "Receiver stopped before HMS permission check completed") + return@addOnSuccessListener + } + + if (granted == true) { + Log.i(tag, "HMS Wear Engine DEVICE_MANAGER permission already granted") + registerOnFirstBondedDevice() + } else { + requestDeviceManagerPermission() + } + } + .addOnFailureListener { error -> + Log.w( + tag, + "Failed to check HMS Wear Engine DEVICE_MANAGER permission; requesting it", + error + ) + requestDeviceManagerPermission() + } + } + private fun requestDeviceManagerPermission() { val callback = object : AuthCallback { override fun onOk(permissions: Array) { Log.i(tag, "HMS Wear Engine permission granted: ${permissions.joinToString()}") + if (!stopped.get()) { + registerOnFirstBondedDevice() + } } override fun onCancel() { @@ -137,8 +169,18 @@ class WearEngineReceiver( } private fun registerOnFirstBondedDevice() { + if (stopped.get()) { + Log.d(tag, "Receiver stopped before bonded-device query") + return + } + deviceClient.getBondedDevices() .addOnSuccessListener { devices -> + if (stopped.get()) { + Log.d(tag, "Receiver stopped before bonded-device query completed") + return@addOnSuccessListener + } + if (devices.isNullOrEmpty()) { Log.w(tag, "No Huawei Health bonded devices visible to HMS Wear Engine") return@addOnSuccessListener @@ -153,8 +195,19 @@ class WearEngineReceiver( } private fun registerReceiver(device: Device) { + if (stopped.get()) { + Log.d(tag, "Receiver stopped before HMS receiver registration") + return + } + p2pClient.registerReceiver(device, receiver) .addOnSuccessListener { + if (stopped.get()) { + Log.d(tag, "Receiver stopped after HMS registration completed; unregistering") + p2pClient.unregisterReceiver(receiver) + return@addOnSuccessListener + } + registeredDevice = device Log.i(tag, "Registered HMS Wear Engine receiver for bonded wearable") }