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>
This commit is contained in:
n0tst3v3
2026-08-19 10:27:15 -06:00
co-authored by Claude Opus 5
parent 0f01b836c6
commit f16efce66e
5 changed files with 170 additions and 50 deletions
@@ -11,7 +11,7 @@ import java.util.Locale;
public class TrackerDatabase extends SQLiteOpenHelper {
private static final String DB_NAME = "bluetooth_tracker.db";
private static final int DB_VERSION = 15;
private static final int DB_VERSION = 16;
public TrackerDatabase(Context context) {
super(context, DB_NAME, null, DB_VERSION);
@@ -32,6 +32,7 @@ public class TrackerDatabase extends SQLiteOpenHelper {
point.longitude = cursor.getDouble(cursor.getColumnIndex("longitude"));
point.accuracy = cursor.getFloat(cursor.getColumnIndex("accuracy"));
point.timestamp = cursor.getLong(cursor.getColumnIndex("logged_at"));
point.event = cursor.getString(cursor.getColumnIndex("event"));
result.add(point);
}
} finally { cursor.close(); }
@@ -100,6 +101,8 @@ public class TrackerDatabase extends SQLiteOpenHelper {
addColumn(db, "ALTER TABLE devices ADD COLUMN track_broadcast INTEGER DEFAULT 0");
if (oldVersion < 14) createSupportingTables(db);
if (oldVersion < 15) seedVendors(db);
if (oldVersion < 16)
addColumn(db, "ALTER TABLE location_history ADD COLUMN event TEXT");
}
private void addColumn(SQLiteDatabase db, String sql) {
@@ -108,7 +111,7 @@ public class TrackerDatabase extends SQLiteOpenHelper {
private void createSupportingTables(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS location_history (id INTEGER PRIMARY KEY AUTOINCREMENT," +
"address TEXT,latitude REAL,longitude REAL,accuracy REAL,logged_at INTEGER)");
"address TEXT,latitude REAL,longitude REAL,accuracy REAL,logged_at INTEGER,event TEXT)");
db.execSQL("CREATE INDEX IF NOT EXISTS history_address_time ON location_history(address,logged_at)");
db.execSQL("CREATE TABLE IF NOT EXISTS range_samples (id INTEGER PRIMARY KEY AUTOINCREMENT," +
"address TEXT,session_id TEXT,sampled_at INTEGER,latitude REAL,longitude REAL," +
@@ -166,7 +169,8 @@ public class TrackerDatabase extends SQLiteOpenHelper {
if (old != null) preserveProfile(values, old, fix);
getWritableDatabase().insertWithOnConflict("devices", null, values,
SQLiteDatabase.CONFLICT_REPLACE);
if (fix != null) insertHistory(address, fix, time);
if (fix != null)
insertHistory(address, fix, time, LocationPoint.EVENT_CONNECTED);
}
public synchronized void disconnected(String address, String broadcast, long time,
@@ -177,7 +181,8 @@ public class TrackerDatabase extends SQLiteOpenHelper {
values.put("reason", reason);
values.put("is_tracked", 1);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
if (fix != null) insertHistory(address, fix, time);
if (fix != null)
insertHistory(address, fix, time, LocationPoint.EVENT_DISCONNECTED);
}
public synchronized void telemetry(String address, String broadcast, Integer battery,
@@ -242,16 +247,21 @@ public class TrackerDatabase extends SQLiteOpenHelper {
values.put("has_location", 1);
values.put("updated_at", time);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
insertHistory(address, fix, time);
insertHistory(address, fix, time, null);
}
private void insertHistory(String address, Fix fix, long time) {
/**
* @param event LocationPoint.EVENT_CONNECTED or EVENT_DISCONNECTED for a
* link state change, null for a plain position refresh.
*/
private void insertHistory(String address, Fix fix, long time, String event) {
ContentValues values = new ContentValues();
values.put("address", address);
values.put("latitude", fix.latitude);
values.put("longitude", fix.longitude);
values.put("accuracy", fix.accuracy);
values.put("logged_at", time);
values.put("event", event);
getWritableDatabase().insert("location_history", null, values);
}
@@ -437,6 +447,7 @@ public class TrackerDatabase extends SQLiteOpenHelper {
point.longitude = cursor.getDouble(cursor.getColumnIndex("longitude"));
point.accuracy = cursor.getFloat(cursor.getColumnIndex("accuracy"));
point.timestamp = cursor.getLong(cursor.getColumnIndex("logged_at"));
point.event = cursor.getString(cursor.getColumnIndex("event"));
result.add(point);
}
} finally { cursor.close(); }