Repository navigation
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Subscription leaks and incomplete scan handling can leave scanning active after departure or prevent nearby devices from appearing.
5 open findings
What changed in this PR
Adds a Bluetooth discovery screen alongside the imported-tag list to display nearby Find My devices.
Changes:
- Adds advertisement-key parsing, live sighting aggregation, and scanning.
- Adds device naming, approximate distances, and a localized discovery screen.
- Adds JVM tests for key reconstruction and sighting aggregation.
| File | Description |
|---|---|
| app/src/test/java/dev/wander/android/opentagviewer/ble/NearbyDeviceBookTest.java | Tests aggregation and expiry. |
| app/src/test/java/dev/wander/android/opentagviewer/ble/FindMyFrameKeyTest.java | Tests key reconstruction. |
| app/src/main/res/values/strings.xml | Adds default discovery strings. |
| app/src/main/res/values-zh-rTW/strings.xml | Adds Traditional Chinese strings. |
| app/src/main/res/values-zh-rCN/strings.xml | Adds Simplified Chinese strings. |
| app/src/main/res/values-ru/strings.xml | Adds Russian strings. |
| app/src/main/res/values-nl/strings.xml | Adds Dutch strings. |
| app/src/main/res/values-ko/strings.xml | Adds Korean strings. |
| app/src/main/res/values-ja/strings.xml | Adds Japanese strings. |
| app/src/main/res/values-fr/strings.xml | Adds French strings. |
| app/src/main/res/values-en/strings.xml | Adds English strings. |
| app/src/main/res/values-de/strings.xml | Adds German strings. |
| app/src/main/res/menu/my_devices_menu.xml | Adds scan menu entry. |
| app/src/main/res/layout/nearby_device_list_item.xml | Defines device rows. |
| app/src/main/res/layout/activity_nearby_devices.xml | Defines discovery screen. |
| app/src/main/java/dev/wander/android/opentagviewer/util/nearby/ProximityDistance.java | Estimates distance from signal strength. |
| app/src/main/java/dev/wander/android/opentagviewer/ui/nearbydevices/NearbyDeviceListAdaptor.java | Displays sightings and names. |
| app/src/main/java/dev/wander/android/opentagviewer/NearbyDevicesActivity.java | Coordinates scanning and interaction. |
| app/src/main/java/dev/wander/android/opentagviewer/MyDevicesListActivity.java | Opens discovery screen. |
| app/src/main/java/dev/wander/android/opentagviewer/ble/NearbyDeviceSighting.java | Converts advertisements into sightings. |
| app/src/main/java/dev/wander/android/opentagviewer/ble/NearbyDeviceScanner.java | Scans without imported-tag matching. |
| app/src/main/java/dev/wander/android/opentagviewer/ble/NearbyDeviceBook.java | Groups, sorts, and expires sightings. |
| app/src/main/java/dev/wander/android/opentagviewer/ble/ForeignTagNames.java | Persists prefix-keyed names. |
| app/src/main/java/dev/wander/android/opentagviewer/ble/FindMyFrameKey.java | Reconstructs advertisement keys. |
| app/src/main/AndroidManifest.xml | Registers discovery activity. |
🧠 Review effort: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Owner
|
#260 is merged and makes
|
The app's BLE code only answers "is this one of my tags" - NearbyTagWatcher drops any frame whose address is not in the rolling-key index. This adds the other question, "what is out there at all", behind a new menu entry on the devices list. - ble/FindMyFrameKey: reconstructs the advertisement key from the 0x12 TLV and the BLE address it arrived on, for both payload shapes. Separated (len 0x19) yields the full 28-byte key over MAC + payload; nearby (len 0x02) yields only the six-byte prefix - which is the join key across both shapes inside one rotation, and finer than the MAC alone because it carries the two key bits the address cannot express. Walks the Apple TLV chain rather than reading offset zero, since iPhones prepend their own payloads. - ble/NearbyDeviceSighting + NearbyDeviceScanner: the generic counterpart of NearbyTagWatcher - same hardware scan filter (0x004C + type 0x12), same 20-minute scan restart ahead of the platform's long-scan downgrade, but no index, no MAC resolver, no Python. Emits every frame that parses. - ble/NearbyDeviceBook: one row per key prefix, newest sighting wins, pruned after 30s of silence - a tag that leaves sends nothing, so silence is the only departure signal. Sorted strongest signal first. - ble/ForeignTagNames: user names for foreign devices in SharedPreferences (deliberately not Room - a twelve-hex-chars-to-name map is what it is for, and a schema change would mean a migration). Keyed on the prefix so a name survives the separated/nearby flip; rotation re-keying is a different feature (rotation-tracking branch). - util/nearby/ProximityDistance: RSSI to rough metres (log-distance path loss, -59 dBm at 1m, rendered with "~" - honest about the precision the physics carries). - NearbyDevicesActivity + layout + NearbyDeviceListAdaptor + row layout: the screen the scanner exists for. Subscribes only while on top, rebuilds the list on sightings and on a 1s tick so departures age out live. Long-press renames via the same name dialog the tag page uses. BLE permission is asked once per screen, via BlePermissions like Maps. - Manifest declares the activity; my_devices_menu gains the entry, worded as a scan so nobody mistakes it for a network lookup. - 8 new strings across all 10 locales via add_strings.py. - Tests: FindMyFrameKeyTest pins the key layout for both shapes, the prefix join between them, the non-leading-TLV walk, and rejects junk; NearbyDeviceBookTest pins newest-wins, stale-resend, sort and pruning. Not verified against a physical tag: no AirTag was at hand during development. The key reconstruction follows the layout FindMy.py's scanner.py implements and is pinned by JVM tests; frames where 0x12 does not lead the manufacturer blob are parsed by FindMyFrameKey but currently dropped upstream of it (see NearbyDeviceSighting.tlvBytes). Co-Authored-By: Devin/SWE-2 <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- The hardware filter and the sighting parser both demanded the Find My record at manufacturer-data offset zero, but Apple's TLV chain does not guarantee it: Nearby Info and Handoff records routinely lead. The scanner now filters on the company ID alone and sorts the chain in software (NearbyDeviceSighting reuses FindMyFrameKey.findFindMyTlv), so a 0x12 behind another record still becomes a row. A new NearbyDeviceSightingTest pins that with the prefixed fixture. - Bluetooth going off or a refused scan completed the subscription silently, and the refresh timer then overwrote the reason with "no devices in range". The screen now resubscribes on a 30-second retry - the same cadence MapsActivity.onNearbyWatchEnded uses - and the empty state distinguishes "Bluetooth is off or unavailable" from "nothing heard" via a new string in all locales. - Removed the manual onResume() after the permission grant: the dialog pauses the activity, so the framework resumes it anyway, and the extra call subscribed a second scan whose disposable was then overwritten - onPause could only stop the newer pair. - The MAC row used colorOutline at 12sp, 4.25:1 against the row background; colorOnSurfaceVariant clears the 4.5:1 normal-text floor. Co-Authored-By: Devin/SWE-2 <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- NearbyDeviceBook: silence past GONE_AFTER_MS now marks a row offline instead of dropping it; snapshot returns live rows by RSSI first, then offline rows by most-recently-heard, and only forgets a prefix past RETAIN_GONE_MS (10 min) so a busy day cannot fill the list forever. - The adaptor draws offline rows at half alpha and reports only "offline · <last seen>" - distance, battery and owner-state from the last hearing would read as current conditions. - NearbyDeviceBookTest: offline retention, horizon pruning, and the live-before-offline ordering. - nearby_device_offline in all ten locales. Co-Authored-By: Devin/SWE-2 <158243242+devin-ai-integration[bot]@users.noreply.github.com>
rubo77
force-pushed
the
feature/nearby-scanner
branch
from
October 15, 2026 16:59
14534f3 to
c6326d2
Compare
1 task
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


The app's BLE code only answers "is this one of my tags" - NearbyTagWatcher drops any frame whose address is not in the rolling-key index. This adds the other question, "what is out there at all", behind a new menu entry on the devices list.
Not verified against a physical tag: no AirTag was at hand during development. The key reconstruction follows the layout FindMy.py's scanner.py implements and is pinned by JVM tests; frames where 0x12 does not lead the manufacturer blob are parsed by FindMyFrameKey but currently dropped upstream of it (see NearbyDeviceSighting.tlvBytes).