Compare commits

...
27 Commits
Author SHA1 Message Date
n0tst3v3andClaude Opus 5 60feffc256 feat(track): record which devices are paired, and report it on upload
The app knew about bonded devices only in passing - syncPairedDevices marked
them tracked and threw the bond state away - so nothing downstream could tell
a paired device from one merely seen. Adds devices.paired (schema 17), set
from ACTION_BOND_STATE_CHANGED and from the bonded list at startup, and sends
it as a `paired` column on every uploaded row.

The flag is its own column rather than inferred from is_tracked, which answers
a different question: a device can be tracked without ever having been paired.

syncPairedDevices clears the flag before re-asserting it from getBondedDevices.
A device unpaired while the service was dead never broadcast BOND_NONE, so the
bonded list is the only reliable answer at startup, and without the clear such
a device would stay marked paired forever.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 18:03:11 -06:00
n0tst3v3andClaude Opus 5 07504555b6 build: stop tracking app/build, which shadowed new classes in AIDE
.gitignore has listed /app/build since it was written, but 187 files under it
were already tracked by then, and ignore rules do not apply to tracked files.
So every `git reset --hard origin/main` restored a set of .class files built on
18 August into app/build/bin/classesdebug/.

Those stale classes cover com.wytehat.btlogger and contain no
DeviceRegistration, SecureStore or DeviceIdentity. AIDE resolved the package
against them rather than against the sources beside them and reported
"Unknown entity 'DeviceRegistration'" in ServerUploader - code that compiles
clean against android.jar.

The committed app/build/gen/R.java is the same problem one step along: it
predates the sort_button id, so anything compiling against it fails on a
button that has been in res/layout/main.xml for days.

Removed from the index only. The directory stays on disk, AIDE keeps its
incremental build, and it will not travel between machines again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:20:04 -06:00
n0tst3v3andClaude Opus 5 320b4b2825 feat(upload): register for a per-device key, and fix the upload host
Registration
------------
On first upload the app trades the bootstrap token and its device id at
/api/v1/register-device for a key belonging to this device alone, keeps it in
the Keystore-encrypted store, and uses it from then on. The bootstrap token is
never sent again.

The only secret shipped in the APK now grants enrolment and nothing else.
Extracting it lets someone register a device; it does not let them read points
or upload as a phone that is already enrolled.

A 401 on upload means the key was revoked or rotated elsewhere. The uploader
forgets it, registers again and retries once, rather than leaving the user to
find the Server dialog.

The registration URL is derived from the configured upload URL, so pointing
the app at another server moves both together.

Keystore storage
----------------
The AES-GCM/Keystore code moves out of DeviceIdentity into SecureStore, now
that there are two secrets to keep rather than one. Below API 23 there is no
Keystore AES: SecureStore refuses to write instead of silently storing
secrets in the clear, and both callers keep their value in memory for the
process lifetime.

Upload host
-----------
DEFAULT_URL moves to com.org.bz. bt.justbug.me resolves elsewhere and has no
certificate on the proxy, so every upload there died in the TLS handshake with
TLSV1_ALERT_UNRECOGNIZED_NAME before a request was sent. Installs that already
saved the old default are migrated across on read; a URL the user chose is
left alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:16:08 -06:00
n0tst3v3andClaude Opus 5 97c1e48e91 feat(upload): identify this device to the ingestion server
Sends X-Device-ID on every upload so the server can attribute sightings to the
phone that recorded them; it now rejects uploads without one.

Prefers Settings.Secure.ANDROID_ID: it survives reinstalls, costs nothing to
read and needs no permission. It is not always trustworthy though - null
before first boot completes, and a known family of builds all report the same
constant - so those readings are rejected outright rather than repaired, and a
random UUID is generated and kept instead. Guessing at a malformed reading
would produce an identifier that changes between reads, which is worse than
falling back.

The fallback UUID is stored encrypted under an AES-GCM key held in the
AndroidKeyStore, so the key material never enters the app process and cannot
be lifted out of a backup or off a rooted device. androidx.security's
EncryptedSharedPreferences does exactly this, but it needs AndroidX and API
23; this project builds in AIDE with no dependency resolution and minSdk 14,
so the same construction is done directly against the platform Keystore. The
Keystore calls sit behind SDK_INT checks and are never resolved below 23,
where the identifier is kept in memory for the process lifetime rather than
written out in the clear.

A stored value that will not decrypt - key cleared, or restored onto another
device - yields a new identity rather than a crash. The old one is genuinely
unrecoverable at that point.

Upload errors now surface the server's "error" field instead of the raw JSON
envelope, so a rejected device ID reads as a sentence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:25:56 -06:00
n0tst3v3andClaude Opus 5 308d63b8d8 feat(map): upload recorded sightings to the ingestion server
Adds ServerUploader plus an "Upload to Server" button and a Server…
settings dialog in the map filter panel.

The uploader builds a CSV of every device fix and every recorded history
point - the history rows are the interesting ones, since they carry each
device's location trail - and POSTs it as multipart/form-data with the API
key in X-API-Key. Timestamps are written ISO-8601 in UTC, because sending
local time would shift every point by the phone's offset.

It uploads the whole database every time rather than tracking what was sent
before. The server deduplicates on (mac, timestamp, latitude, longitude), so
re-sending is idempotent: anything it already holds comes back counted as a
duplicate instead of stored twice. Keeping the client dumb is what makes a
failed or partial upload safe to simply retry, with no sync state to get out
of step.

Network work runs on a background thread and the result is delivered on the
main thread; the button disables itself while a run is in flight and the
panel shows the returned metrics.

The URL and key default to the deployment at bt.justbug.me and are editable
in the dialog, stored in the server_settings preferences.

Verified against the real server: the exact CSV this emits - including empty
RSSI cells for history rows and a device name containing a comma - ingests as
4 points, and re-uploading the same file returns 4 duplicates and 0 added.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:26:29 -06:00
n0tst3v3andClaude Opus 5 6808e88599 feat(map): use Font Awesome link icons for the point events
Swaps the emoji for Font Awesome Free 6.5.2 "link" and "link-slash"
(solid), inlined as SVG paths instead of loaded from a CDN. Pulling
the icon font would be a ~75KB network download for two glyphs, and
the popups need to render whether or not that request succeeds.

Inline SVG also takes a fill colour, so connected draws green and
dropped draws red, and it renders identically regardless of the
device's emoji font - 💔 was a stand-in because the literal broken
chain (U+26D3 U+FE0F U+200D U+1F4A5) is a 2023 addition that shows
as a blank box on older Android.

Both paths verified byte-for-byte against upstream.

Icons: CC BY 4.0, Copyright 2024 Fonticons, Inc.
https://fontawesome.com/license/free

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 10:48:48 -06:00
n0tst3v3andClaude Opus 5 f16efce66e perf(scanner): stop redoing everything on every advertisement, plus map point events
Yes, the scanner was duplicating. refreshCategoryOptions(),
updateRadar() and pairedAdapter.notifyDataSetChanged() ran from
acceptSignal on every advertisement AND from pruneTask once a second
- the same three calls either way.

Worse, the whole persist path ran per advertisement, and ScanCallback
is delivered on the main looper: a hex dump of the advertisement,
lookupCompany(), get(), lookupOui(), then telemetry(),
setDetectedCategory(), recordObservation() and setVendor(). Eight
database operations on the UI thread per beacon, at
SCAN_MODE_LOW_LATENCY, times every transmitter in range. The radar
sweep re-posts itself every 33ms and was competing for that thread,
which is why it dragged.

A device already on the radar now only gets its RSSI and last-seen
stamp refreshed until PERSIST_INTERVAL_MS (3s) is up. A device's
first sighting still takes the full path and refreshes at once.

The radar view also allocated per frame at 30fps: a six-stop
RadialGradient plus two arrays, and a fresh ArrayList and Comparator
for the target sort. Both reused now. setTargets no longer
invalidates while scanning, since onDraw already re-posts itself.
Left setLayerType(SOFTWARE) alone - setShadowLayer only works on text
under hardware acceleration, so removing it would drop the ring glow.

Separately, history points now say why they exist. location_history
had no event column, so schema 16 adds one; connected() and
disconnected() stamp it and a plain position refresh leaves it null.
The popup shows a linked or broken-link line for the two states and
stays quiet for older rows that predate the column.

History points also render as the device's own marker at half size
rather than a plain circle, sharing one divIcon per device so a long
trail does not build a DOM node per point.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 10:27:15 -06:00
n0tst3v3andClaude Opus 5 0f01b836c6 fix(map): un-clip the date buttons, link every popup's coordinates
Date/time buttons carry a two-line label ("From\nAug 19, 2026") but
sat in a fixed dp(40) row, so the date line was cut off and only
From/To was readable. My own compaction pass tightened that row from
48dp to 40dp and made it worse. They now measure wrap-content, at
11sp, with all-caps off (Buttons upper-case by default, which is
what pushed the date past the edge) and 4dp side padding instead of
the stock ~16dp.

Historical, normalized and removed points now render their
coordinates as the same tappable geo: link the live device popup
already used - MapActivity's WebViewClient hands geo: to the maps
app, so all four popups behave the same. Factored into gpsLink().

Drops the "View: Normalized" line from the device popup; the filter
panel already says which view is active.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 09:39:38 -06:00
n0tst3v3andClaude Opus 5 be517f56a5 fix(map): bring back the compact filter button and floating layers control
The split carried over the wrong versions of two bits of chrome.

The filter toggle had become a full-width grey TextView bar, 48dp of
"Map Filters ▼" sitting above the map. Restored to what it was: a
small "☰  Filters" button on a wrap-content row.

The Leaflet layers control was being created with collapsed:false,
which pins the whole Streets/Satellite list open as a box over the
map. collapsed:true is what renders it as the floating corner button
that opens on tap. Its 13px font rule came back with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 09:30:45 -06:00
n0tst3v3andClaude Opus 5 655b01be50 fix(map): make the filter panel scroll, and tighten it up
The panel is taller than a phone screen, and it was a LinearLayout
child with WRAP_CONTENT and no weight - so the excess was simply
clipped at the bottom edge. Everything from the accuracy multiplier
down was unreachable, which is where Apply, Reset, Export Raw and
Export Normalized had gone. Nothing was missing from the build; it
was drawn off-screen.

Wrap it in a ScrollView carrying the layout weight, so it shares the
screen with the map and scrolls to the rest. setFiltersExpanded now
hides the scroller rather than the panel inside it, or the collapsed
state would leave an empty scroller holding half the screen.

Then cut roughly 145dp of height out of the panel so most of it fits
without scrolling at all: the threshold readout goes from five lines
to two, the normalization summary from six to two (the level and
percentage are already on the line above it), button rows 48dp ->
40dp, the slider 42dp -> 28dp, and the section labels and paddings
lose a few dp each.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 09:27:12 -06:00
n0tst3v3andClaude Opus 5 5d98f474ae fix(live): stop the item pin from tracking the bearing arrow
The pin sat dead ahead of the heading arrow no matter which way the
user turned, so the arrow looked like it was aiming at the item and
the item looked like it was always straight down the path of travel.

The cause is geometric, not a UI link. addSample() only accepts a
sample after 5 m of movement, so every sample lies on the line the
user walked. gradientBearing() correlates RSSI against centred
position, and on a collinear track each centred position is t*u for
a single unit vector u along the walk - so the sum is exactly
parallel to u regardless of the signal. The "estimated" bearing was
the direction of travel, echoed back.

That bearing then placed the pin in three separate paths:
preliminaryEstimate() projects along it, solve() overrides a
converged trilateration with it, and consensusEstimate() averages
the result. All three produced a pin straight ahead.

Refuse the bearing until the track has real width across its own
axis - the smaller eigenvalue of the position covariance, against
max(4 m, meanAccuracy/2). A straight walk with GPS jitter measures
about 1 m of spread and is rejected; an L of two 20 m legs measures
about 5 m and is accepted. Distances still place the item off-axis
on a straight walk, but which side is a real mirror ambiguity, so
the UI now says so and asks for a leg at 90 degrees instead of
inventing a side.

Also drops showFallbackPin(), which projected the pin along the raw
compass heading whenever no estimate was solved - a fourth route to
the same wrong place. With no estimate the pin now shows the item's
last known GPS fix, or nothing at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 09:10:26 -06:00
n0tst3v3andClaude Opus 5 15baafc9be refactor(map): split the map screen into four focused classes
MapActivity had grown to 4,800 lines and was editing itself into
corruption - the field block had picked up seventeen duplicate
declarations of showNormalizationChanges, and that name was being
used for both the CheckBox widget and the boolean state it toggles,
which is what the compiler was actually failing on.

Fix the collision the way the class already handles the same pattern
elsewhere (showNormalized/normalizedMode): the widget is showChanges,
the state stays showNormalizationChanges.

Then move the bulk out of the activity:

  HistoryNormalizer  the GPS cleanup engine, its caches, thresholds
                     and the LocationPoint reflection helpers
  MapExporter        CSV export, including the 695-line writer and
                     the file-picker round trip
  MapHtmlBuilder     the Leaflet page, markers, trails and popups
  MapFormat          date/reason/colour/HTML/JS/CSV escaping shared
                     by all three

The activity keeps the UI - onCreate, the filter panel, pickers and
sensors - and feeds the builders through a Filters snapshot, an
Options struct and a two-method DataSource interface, so none of the
extracted classes reach back into its widgets.

Behaviour is unchanged. Also drops pendingExportMode, which was
written in two places and never read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 04:56:19 -06:00
n0tst3v3 00703e013c fix(live): restore the moving navigation pin, aimed at the signal gradient 2026-08-19 04:01:30 -06:00
n0tst3v3 57861a19c6 fix: restore AIDE build config and layer live find map correctly 2026-08-19 03:50:02 -06:00
n0tst3v3 feb32c9cd1 fix(ui): support foldables and large screens instead of running in compat mode 2026-08-19 03:32:34 -06:00
n0tst3v3 340a32c301 fix(live): point the heading arrow at true north and keep the phone marker on top 2026-08-19 03:13:43 -06:00
n0tst3v3 f711e5ee36 build: modernize Gradle to AGP 8.5 with wrapper and namespace 2026-08-19 03:13:43 -06:00
n0tst3v3 48b40136cf fix: restore NavigationHelper.java clobbered by a pasted Python script 2026-08-19 02:26:16 -06:00
n0tst3v3 564877e1e7 fix(map): make filter panel compact and scrollable 2026-08-19 00:57:05 -06:00
n0tst3v3 8d3d571954 feat(alerts): keep alerting until left-behind items are acknowledged 2026-08-19 00:57:05 -06:00
n0tst3v3 a48cc91680 feat(list): sort tracked items by last seen with reverse toggle 2026-08-19 00:56:47 -06:00
n0tst3v3 a5f5d415e2 feat(map): add GPS point downsampling and UI density control 2026-08-19 00:56:47 -06:00
n0tst3v3 a2c74dc52f chore(map): add missing package declaration to MapActivity 2026-08-19 00:56:38 -06:00
n0tst3v3 2ced76049b Revert "fix(map): make filter panel compact and scrollable"
This reverts commit dc63fffb67.
2026-08-19 00:54:47 -06:00
n0tst3v3 dc63fffb67 fix(map): make filter panel compact and scrollable 2026-08-19 00:50:25 -06:00
n0tst3v3 d5150ba126 Revert "feat(map): add GPS point downsampling and UI density control"
This reverts commit c15dd28f3b.
2026-08-19 00:49:20 -06:00
n0tst3v3 b8401b3613 Revert "feat(ui): update item sorting, persistent notifications, and compact map sampling UI"
This reverts commit ca3aaaf163.
2026-08-19 00:49:20 -06:00
213 changed files with 6145 additions and 4806 deletions
+3
View File
@@ -1 +1,4 @@
/build /build
.gradle
/app/build
local.properties
+450
View File
@@ -0,0 +1,450 @@
# BlueToothLogger Android Application - Technical Project Context
## Project Overview
**Project Name:** BlueToothLogger
**Package:** `com.wytehat.btlogger`
**Location:** `/Users/n0tst3v3/bletracker/BlueToothLogger`
**Type:** Bluetooth tracking and logging application with spatial analysis capabilities
---
## Core Classes and Inheritance
| Class | Extends | Implements | Purpose |
|-------|---------|------------|---------|
| `MainActivity` | Activity | ItemActionListener | Main entry point, UI controller |
| `BluetoothTrackingService` | Service | LocationListener | Background Bluetooth tracking |
| `TrackerDatabase` | SQLiteOpenHelper | - | SQLite database wrapper |
| `MapActivity` | Activity | - | Map visualization |
| `DeviceManagerActivity` | Activity | - | Device management UI |
| `LiveRangeFinderActivity` | Activity | LocationListener, SensorEventListener | Real-time signal finder |
| `BluetoothRadarView` | View | - | Custom radar visualization |
| `DeviceListAdapter` | BaseAdapter | - | List adapter for devices |
---
## BluetoothTrackingService - Technical Details
### Constants
```java
public static final String ACTION_DATA_CHANGED = "com.wytehat.btlogger.DATA_CHANGED"
public static final String ACTION_CONFIG_CHANGED = "com.wytehat.btlogger.CONFIG_CHANGED"
public static final String ACTION_RANGE_MODE = "com.wytehat.btlogger.RANGE_MODE"
private static final int NOTIFICATION_ID = 42
private static final String CHANNEL_ID = "bluetooth_tracking"
private static final String ALERT_CHANNEL_ID = "tracked_item_disconnects"
```
### Fields
```java
private TrackerDatabase db
private LocationManager locationManager
private BluetoothAdapter bluetooth
private Location lastLocation
private boolean receiverRegistered
private boolean rangeModeActive
private int phoneBattery = 100
private final Handler handler = new Handler()
private final HashMap<String, Long> lastPeriodicLog
```
### Key Methods
```java
public void onCreate()
public int onStartCommand(Intent intent, int flags, int startId)
public void onDestroy()
public IBinder onBind(Intent intent)
// Location callbacks
public void onLocationChanged(Location location)
public void onProviderDisabled(String provider)
public void onProviderEnabled(String provider)
public void onStatusChanged(String provider, int status, Bundle extras)
// Bluetooth callbacks
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord)
// Private methods
private void loadLastLocation()
private void configureLocation()
private int shortestActiveInterval()
private void stopLocation()
private void captureFresh(String address, long eventTime)
private TrackerDatabase.Fix currentFix()
private TrackerDatabase.Fix fix(Location location)
private void resolveVendor(String address, int companyId)
private String bytesToHex(byte[] data)
private int parseCompanyId(byte[] scanRecord)
private String infer(DeviceRecord record)
private void disconnectAll(String reason)
private void syncPairedDevices()
private void restartBackgroundScan()
private boolean shouldBackgroundScan()
private boolean isLocationEligible(DeviceRecord record, long now)
private void notifyChanged()
private boolean hasLocationPermission()
private boolean hasBluetoothPermission()
private String address(BluetoothDevice device)
private String name(BluetoothDevice device)
private Location newer(Location first, Location second)
private void showDisconnectAlert(DeviceRecord record, String reason)
private Notification buildNotification()
```
---
## TrackerDatabase - Technical Details
### Constants
```java
private static final String DB_NAME = "bluetooth_tracker.db"
private static final int DB_VERSION = 15
```
### Inner Class: Fix
```java
public static class Fix {
public double latitude
public double longitude
public float accuracy
public Fix(double lat, double lon, float acc)
}
```
### Key Methods
```java
public TrackerDatabase(Context context)
public void onCreate(SQLiteDatabase db)
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
// Device tracking
public synchronized void connected(String address, String broadcast, long time, Fix fix)
public synchronized void disconnected(String address, String broadcast, long time, String reason)
public synchronized void telemetry(String address, String broadcast, Integer battery, String vendor, String category, long time)
public synchronized void recordObservation(String address, long time, int rssi, int txPower)
public synchronized void updateLocation(String address, Fix fix, long time)
// Profile and calibration
public synchronized void updateProfile(String address, String title, String photo, String color, long time)
public synchronized void updateCalibration(String address, int txPower, double exponent)
// Range finding
public synchronized void logRangeSample(String address, String sessionId, long time, int rssi, double distance)
// Tracking settings
public synchronized void updateTracking(String address, int minutes, String color)
public synchronized void markTracked(String address, String broadcast)
public synchronized void removeFromTracking(String address)
public synchronized void setMapEnabled(String address, boolean enabled)
public synchronized void setLiveEnabled(String address, boolean enabled)
public synchronized void setTrackBroadcast(String address, boolean enabled)
public synchronized void setShowTrail(String address, boolean shown)
public synchronized void setLocationLocked(String address, boolean locked)
public synchronized void setCustomName(String address, String name)
public synchronized void setDetectedCategory(String address, String category)
public synchronized void setCategory(String address, String category)
public synchronized void setVendor(String address, String vendor)
public synchronized void setManualVendor(String address, String vendor)
// Lookups
public synchronized String lookupOui(String address)
public synchronized String lookupCompany(int companyId)
// Queries
public synchronized DeviceRecord get(String address)
public synchronized List<DeviceRecord> all()
public synchronized List<DeviceRecord> tracked()
public synchronized List<LocationPoint> history(String address, long since)
```
---
## MainActivity - Technical Details
### Constants
```java
private static final int PERMISSIONS = 100
private static final int CAMERA = 200
```
### Fields
```java
private TrackerDatabase db
private DeviceListAdapter adapter
private boolean registered
private DeviceRecord editing
private final Handler refreshHandler = new Handler()
private final Runnable refreshLiveAvailability
private final BroadcastReceiver updates
```
### Key Methods
```java
protected void onCreate(Bundle state)
protected void onResume()
protected void onPause()
protected void onDestroy()
// UI actions
public void edit(DeviceRecord record)
public void map(DeviceRecord record)
public void live(DeviceRecord record)
public void manage(DeviceRecord record)
// Device operations
private void removeTrackedItem(DeviceRecord record)
private void toggleBroadcastTracking(DeviceRecord record)
private void toggleTrail(DeviceRecord record)
private void refind(DeviceRecord record)
// Renaming and settings
private void rename(DeviceRecord record)
private void chooseTracking(DeviceRecord record)
private void chooseColor(DeviceRecord record, int minutes)
private void takePhoto()
protected void onActivityResult(int request, int result, Intent data)
private void showSettings()
private void openNotificationSettings()
private void openAppSettings(String instruction)
private boolean isBatteryOptimizationDisabled()
private void requestBackgroundProtection()
private void showTrackingInformation()
// Permissions
public void onRequestPermissionsResult(int request, String[] permissions, int[] results)
private void requestNeededPermissions()
private void addPermission(List<String> list, String permission)
private void reload()
```
---
## LiveRangeFinderActivity - Technical Details
### Implements
```java
implements LocationListener, SensorEventListener, ItemActionListener
```
### Key Methods
```java
protected void onCreate(Bundle savedState)
protected void onPause()
protected void onDestroy()
// Location callbacks
public void onLocationChanged(Location location)
public void onProviderDisabled(String provider)
public void onProviderEnabled(String provider)
public void onStatusChanged(String provider, int status, Bundle extras)
// Sensor callbacks
public void onSensorChanged(SensorEvent event)
public void onAccuracyChanged(Sensor sensor, int accuracy)
// Bluetooth callbacks
public void onScanResult(int callbackType, ScanResult result)
public void onBatchScanResults(List<ScanResult> results)
public void onScanFailed(int errorCode)
// UI actions
private void startSearch()
private void stopSearch(String message)
private void tagItemHere()
private void startCalibration()
private void updateRadar()
private void updateRangeDisplay()
private void updateDistanceDisplay()
private void updateCalibrationDisplay()
private void updateSearchDisplay()
private void updateSearchStatus()
private void updateSearchProgress()
private void updateSearchResults()
private void updateSearchSettings()
private void updateSearchControls()
private void updateSearchUI()
```
---
## MapActivity - Technical Details
### Implements
```java
implements LocationListener, SensorEventListener, ItemActionListener
```
### Key Methods
```java
protected void onCreate(Bundle state)
protected void onResume()
protected void onPause()
protected void onDestroy()
// Location callbacks
public void onLocationChanged(Location location)
public void onProviderDisabled(String provider)
public void onProviderEnabled(String provider)
public void onStatusChanged(String provider, int status, Bundle extras)
// Sensor callbacks
public void onSensorChanged(SensorEvent event)
public void onAccuracyChanged(Sensor sensor, int accuracy)
// Map operations
private void updateMap()
private void updateMarkers()
private void updateTrails()
private void updateCamera()
private void updateLocation()
private void updateRange()
private void updateCalibration()
private void updateSearch()
private void updateSettings()
private void updateUI()
```
---
## DeviceManagerActivity - Technical Details
### Key Methods
```java
protected void onCreate(Bundle state)
protected void onPause()
protected void onDestroy()
// Bluetooth scanning
private void startScan()
private void stopScan()
private void refreshCategoryOptions()
private void updateRadar()
// Device management
private void toggleMap(DeviceRecord record, Button button)
private void toggleTrack(DeviceRecord record, Button button)
private void editSignal(DeviceRecord record)
private void confirmRemoveSignal(SignalEntry entry)
private void copyText(String label, String text)
// Adapters
private class SignalGridAdapter extends BaseAdapter
private class PairedAdapter extends BaseAdapter
```
---
## BluetoothRadarView - Technical Details
### Extends
```java
extends View
```
### Key Features
- Custom radar-like visualization
- Radial gradient rendering
- Touch event handling
- Signal strength visualization
---
## Data Models
### DeviceRecord
```java
public class DeviceRecord {
public String address
public String title
public String broadcast
public String category
public String vendor
public String color
public int trackingMinutes
public boolean mapEnabled
public boolean liveEnabled
public boolean trackBroadcast
public boolean showTrail
public boolean locationLocked
public int txPower
public double exponent
public long lastSeen
public long lastLocation
public int battery
public String photo
}
```
### LocationPoint
```java
public class LocationPoint {
public String address
public double latitude
public double longitude
public float accuracy
public long time
}
```
---
## Key Android Components
### Permissions Required
- `BLUETOOTH` - Bluetooth scanning and discovery
- `BLUETOOTH_ADMIN` - Bluetooth administration
- `ACCESS_FINE_LOCATION` - Precise location access
- `ACCESS_COARSE_LOCATION` - Approximate location access
- `FOREGROUND_SERVICE` - Background service execution
- `CAMERA` - Device photo capture
- `RECEIVE_BOOT_COMPLETED` - Auto-start on boot
### Services
- `BluetoothTrackingService` - Foreground service for continuous tracking
- `LocationManager` - GPS and network location services
- `BluetoothAdapter` - Bluetooth scanning and discovery
### Broadcast Receivers
- `DATA_CHANGED` - Device data updates
- `CONFIG_CHANGED` - Configuration changes
- `RANGE_MODE` - Range finder mode changes
- `BOOT_COMPLETED` - System boot completion
---
## Signal Processing Flow
```
Bluetooth Scan → LeScanCallback → RSSI Reading
BluetoothTrackingService
captureFresh() → Fix()
TrackerDatabase.recordObservation()
SpatialGradientEngine Analysis
RangeStateMachine State Update
MapActivity / BluetoothRadarView Update
```
---
## Database Schema (Version 15)
### Tables
- `devices` - Device records and metadata
- `locations` - GPS location history
- `observations` - RSSI observations
- `telemetry` - Battery and vendor info
- `calibrations` - TX power and exponent
- `range_samples` - Range finding data
- `vendors` - Vendor lookup table
- `categories` - Device categories
---
*Document generated from technical analysis of Java source files*
Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More