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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
07504555b6
commit
60feffc256
@@ -117,11 +117,14 @@ public class BluetoothTrackingService extends Service implements LocationListene
|
||||
} else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
|
||||
int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
|
||||
BluetoothDevice.ERROR);
|
||||
if (state == BluetoothDevice.BOND_BONDED)
|
||||
if (state == BluetoothDevice.BOND_BONDED) {
|
||||
db.markTracked(address, broadcastName);
|
||||
else if (state == BluetoothDevice.BOND_NONE)
|
||||
db.setPaired(address, true);
|
||||
} else if (state == BluetoothDevice.BOND_NONE) {
|
||||
db.setPaired(address, false);
|
||||
db.disconnected(address, broadcastName, now, fix,
|
||||
"Manual / Intentional Disconnect");
|
||||
}
|
||||
}
|
||||
resolveVendor(address, -1);
|
||||
notifyChanged();
|
||||
@@ -370,10 +373,16 @@ public class BluetoothTrackingService extends Service implements LocationListene
|
||||
private void syncPairedDevices() {
|
||||
if (bluetooth == null) return;
|
||||
try {
|
||||
// The bonded list is the whole truth, so the flag is cleared first:
|
||||
// a device unpaired while the service was dead sent no BOND_NONE
|
||||
// broadcast, and would otherwise stay marked as paired forever.
|
||||
db.clearPaired();
|
||||
|
||||
for (BluetoothDevice device : bluetooth.getBondedDevices()) {
|
||||
String value = address(device);
|
||||
if (value != null) {
|
||||
db.markTracked(value, name(device));
|
||||
db.setPaired(value, true);
|
||||
resolveVendor(value, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ public class DeviceRecord {
|
||||
public int locationLocked;
|
||||
public int showTrail = 1;
|
||||
public int trackBroadcast;
|
||||
|
||||
/** 1 when this device is bonded to the phone. */
|
||||
public int paired;
|
||||
public String reason;
|
||||
public String photoUri;
|
||||
public String iconPreset;
|
||||
|
||||
@@ -51,7 +51,7 @@ public final class ServerUploader {
|
||||
|
||||
/** Columns the server's importer recognises without any aliasing. */
|
||||
private static final String HEADER =
|
||||
"mac_address,device_name,device_class,rssi,latitude,longitude,timestamp";
|
||||
"mac_address,device_name,device_class,rssi,latitude,longitude,timestamp,paired";
|
||||
|
||||
private static final int CONNECT_TIMEOUT_MS = 20000;
|
||||
private static final int READ_TIMEOUT_MS = 60000;
|
||||
@@ -177,7 +177,8 @@ public final class ServerUploader {
|
||||
record.latitude, record.longitude,
|
||||
record.updatedAt > 0
|
||||
? record.updatedAt
|
||||
: System.currentTimeMillis());
|
||||
: System.currentTimeMillis(),
|
||||
record.paired == 1);
|
||||
}
|
||||
|
||||
List<LocationPoint> history = database.history(record.address, 0L);
|
||||
@@ -198,7 +199,8 @@ public final class ServerUploader {
|
||||
// empty rather than repeating the device's latest reading,
|
||||
// which was measured somewhere else entirely.
|
||||
appendRow(csv, stamp, record.address, name, category, "",
|
||||
point.latitude, point.longitude, time);
|
||||
point.latitude, point.longitude, time,
|
||||
record.paired == 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +210,7 @@ public final class ServerUploader {
|
||||
private static void appendRow(StringBuilder csv, SimpleDateFormat stamp,
|
||||
String mac, String name, String category,
|
||||
String rssi, double latitude,
|
||||
double longitude, long time) {
|
||||
double longitude, long time, boolean paired) {
|
||||
|
||||
csv.append(field(mac)).append(',')
|
||||
.append(field(name)).append(',')
|
||||
@@ -216,7 +218,8 @@ public final class ServerUploader {
|
||||
.append(rssi).append(',')
|
||||
.append(latitude).append(',')
|
||||
.append(longitude).append(',')
|
||||
.append(stamp.format(new Date(time)))
|
||||
.append(stamp.format(new Date(time))).append(',')
|
||||
.append(paired ? "1" : "0")
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = 16;
|
||||
private static final int DB_VERSION = 17;
|
||||
|
||||
public TrackerDatabase(Context context) {
|
||||
super(context, DB_NAME, null, DB_VERSION);
|
||||
@@ -47,7 +47,7 @@ public class TrackerDatabase extends SQLiteOpenHelper {
|
||||
"reason TEXT,updated_at INTEGER DEFAULT 0,photo_uri TEXT,icon_preset TEXT DEFAULT 'bag'," +
|
||||
"gps_polling_rate INTEGER DEFAULT 900000,pin_color TEXT DEFAULT '#E53935'," +
|
||||
"tracking_priority INTEGER DEFAULT 15,photo_path TEXT,device_gps_interval INTEGER DEFAULT 15," +
|
||||
"is_tracked INTEGER DEFAULT 0,custom_name TEXT,broadcast_name TEXT,vendor_name TEXT,tx_power_1m INTEGER DEFAULT -59,path_loss_exponent REAL DEFAULT 2.7,last_rssi_at INTEGER DEFAULT 0,device_category TEXT,category_manual INTEGER DEFAULT 0,vendor_manual INTEGER DEFAULT 0,map_enabled INTEGER DEFAULT 0,live_enabled INTEGER DEFAULT 0,location_locked INTEGER DEFAULT 0,show_trail INTEGER DEFAULT 1,track_broadcast INTEGER DEFAULT 0)");
|
||||
"is_tracked INTEGER DEFAULT 0,custom_name TEXT,broadcast_name TEXT,vendor_name TEXT,tx_power_1m INTEGER DEFAULT -59,path_loss_exponent REAL DEFAULT 2.7,last_rssi_at INTEGER DEFAULT 0,device_category TEXT,category_manual INTEGER DEFAULT 0,vendor_manual INTEGER DEFAULT 0,map_enabled INTEGER DEFAULT 0,live_enabled INTEGER DEFAULT 0,location_locked INTEGER DEFAULT 0,show_trail INTEGER DEFAULT 1,track_broadcast INTEGER DEFAULT 0,paired INTEGER DEFAULT 0)");
|
||||
createSupportingTables(db);
|
||||
seedVendors(db);
|
||||
}
|
||||
@@ -103,6 +103,8 @@ public class TrackerDatabase extends SQLiteOpenHelper {
|
||||
if (oldVersion < 15) seedVendors(db);
|
||||
if (oldVersion < 16)
|
||||
addColumn(db, "ALTER TABLE location_history ADD COLUMN event TEXT");
|
||||
if (oldVersion < 17)
|
||||
addColumn(db, "ALTER TABLE devices ADD COLUMN paired INTEGER DEFAULT 0");
|
||||
}
|
||||
|
||||
private void addColumn(SQLiteDatabase db, String sql) {
|
||||
@@ -331,6 +333,24 @@ public class TrackerDatabase extends SQLiteOpenHelper {
|
||||
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
|
||||
}
|
||||
|
||||
/**
|
||||
* Records whether this device is bonded to the phone.
|
||||
*
|
||||
* Kept as its own column rather than inferred from is_tracked: a device can
|
||||
* be tracked without ever having been paired, and the two answer different
|
||||
* questions.
|
||||
*/
|
||||
/** Clears the flag on every device, before the bonded list re-asserts it. */
|
||||
public synchronized void clearPaired() {
|
||||
ContentValues values = new ContentValues(); values.put("paired", 0);
|
||||
getWritableDatabase().update("devices", values, null, null);
|
||||
}
|
||||
|
||||
public synchronized void setPaired(String address, boolean paired) {
|
||||
ContentValues values = new ContentValues(); values.put("paired", paired ? 1 : 0);
|
||||
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
|
||||
}
|
||||
|
||||
public synchronized void setMapEnabled(String address, boolean enabled) {
|
||||
ContentValues values = new ContentValues(); values.put("map_enabled", enabled ? 1 : 0);
|
||||
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
|
||||
@@ -517,6 +537,7 @@ public class TrackerDatabase extends SQLiteOpenHelper {
|
||||
r.locationLocked = integer(c, "location_locked");
|
||||
r.showTrail = integer(c, "show_trail");
|
||||
r.trackBroadcast = integer(c, "track_broadcast");
|
||||
r.paired = integer(c, "paired");
|
||||
r.connected = integer(c, "connected");
|
||||
r.connectedAt = number(c, "connected_at");
|
||||
r.disconnectedAt = number(c, "disconnected_at");
|
||||
|
||||
Reference in New Issue
Block a user