Files
BlueToothLogger/app/src/main/java/com/wytehat/btlogger/TrackerDatabase.java
T

573 lines
29 KiB
Java

package com.wytehat.btlogger;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.ArrayList;
import java.util.List;
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;
public TrackerDatabase(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public synchronized List<LocationPoint> history(String address, long historyFrom, long historyTo)
{
ArrayList<LocationPoint> result = new ArrayList<LocationPoint>();
if (address == null) return result;
Cursor cursor = getReadableDatabase().query("location_history", null,
"address=? AND logged_at>=? AND logged_at<=?",
new String[] { address, String.valueOf(historyFrom), String.valueOf(historyTo) },
null, null, "logged_at ASC");
try {
while (cursor.moveToNext()) {
LocationPoint point = new LocationPoint();
point.latitude = cursor.getDouble(cursor.getColumnIndex("latitude"));
point.longitude = cursor.getDouble(cursor.getColumnIndex("longitude"));
point.accuracy = cursor.getFloat(cursor.getColumnIndex("accuracy"));
point.timestamp = cursor.getLong(cursor.getColumnIndex("logged_at"));
result.add(point);
}
} finally { cursor.close(); }
return result;
}
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE devices (address TEXT PRIMARY KEY,name TEXT," +
"connected INTEGER NOT NULL DEFAULT 0,connected_at INTEGER DEFAULT 0," +
"disconnected_at INTEGER DEFAULT 0,latitude REAL,longitude REAL,accuracy REAL," +
"has_location INTEGER DEFAULT 0,battery INTEGER DEFAULT -1,rssi INTEGER DEFAULT 0," +
"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)");
createSupportingTables(db);
seedVendors(db);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 2) {
addColumn(db, "ALTER TABLE devices ADD COLUMN photo_uri TEXT");
addColumn(db, "ALTER TABLE devices ADD COLUMN icon_preset TEXT DEFAULT 'bag'");
addColumn(db, "ALTER TABLE devices ADD COLUMN gps_polling_rate INTEGER DEFAULT 900000");
}
if (oldVersion < 3) {
addColumn(db, "ALTER TABLE devices ADD COLUMN pin_color TEXT DEFAULT '#E53935'");
addColumn(db, "ALTER TABLE devices ADD COLUMN tracking_priority INTEGER DEFAULT 15");
addColumn(db, "ALTER TABLE devices ADD COLUMN photo_path TEXT");
addColumn(db, "ALTER TABLE devices ADD COLUMN device_gps_interval INTEGER DEFAULT 15");
addColumn(db, "ALTER TABLE devices ADD COLUMN is_tracked INTEGER DEFAULT 0");
addColumn(db, "ALTER TABLE devices ADD COLUMN custom_name TEXT");
addColumn(db, "ALTER TABLE devices ADD COLUMN broadcast_name TEXT");
addColumn(db, "ALTER TABLE devices ADD COLUMN vendor_name TEXT");
db.execSQL("UPDATE devices SET broadcast_name=name,photo_path=photo_uri," +
"device_gps_interval=CASE WHEN gps_polling_rate=0 THEN 0 ELSE gps_polling_rate/60000 END," +
"tracking_priority=CASE WHEN gps_polling_rate=0 THEN 0 ELSE gps_polling_rate/60000 END," +
"is_tracked=CASE WHEN connected=1 OR connected_at>0 OR photo_uri IS NOT NULL THEN 1 ELSE 0 END");
createSupportingTables(db);
seedVendors(db);
}
if (oldVersion < 4) {
addColumn(db, "ALTER TABLE devices ADD COLUMN tx_power_1m INTEGER DEFAULT -59");
addColumn(db, "ALTER TABLE devices ADD COLUMN path_loss_exponent REAL DEFAULT 2.7");
createSupportingTables(db);
}
if (oldVersion < 5) {
addColumn(db, "ALTER TABLE devices ADD COLUMN last_rssi_at INTEGER DEFAULT 0");
}
if (oldVersion < 6) addColumn(db, "ALTER TABLE devices ADD COLUMN device_category TEXT");
if (oldVersion < 7) addColumn(db, "ALTER TABLE devices ADD COLUMN category_manual INTEGER DEFAULT 0");
if (oldVersion < 8) addColumn(db, "ALTER TABLE devices ADD COLUMN vendor_manual INTEGER DEFAULT 0");
if (oldVersion < 9) db.execSQL("UPDATE devices SET updated_at=0 WHERE last_rssi_at=0 AND rssi=0 AND connected_at=0 AND disconnected_at=0 AND has_location=0 AND battery<0");
if (oldVersion < 10) {
addColumn(db, "ALTER TABLE devices ADD COLUMN map_enabled INTEGER DEFAULT 0");
addColumn(db, "ALTER TABLE devices ADD COLUMN live_enabled INTEGER DEFAULT 0");
db.execSQL("UPDATE devices SET map_enabled=1 WHERE has_location=1 AND is_tracked=1");
}
if (oldVersion < 11) addColumn(db, "ALTER TABLE devices ADD COLUMN location_locked INTEGER DEFAULT 0");
if (oldVersion < 12) {
addColumn(db, "ALTER TABLE devices ADD COLUMN show_trail INTEGER DEFAULT 1");
db.execSQL("UPDATE devices SET show_trail=0 WHERE location_locked=1");
}
if (oldVersion < 13)
addColumn(db, "ALTER TABLE devices ADD COLUMN track_broadcast INTEGER DEFAULT 0");
if (oldVersion < 14) createSupportingTables(db);
if (oldVersion < 15) seedVendors(db);
}
private void addColumn(SQLiteDatabase db, String sql) {
try { db.execSQL(sql); } catch (Exception ignored) { }
}
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)");
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," +
"accuracy REAL,raw_rssi INTEGER,filtered_rssi REAL,heading REAL,range_state TEXT)");
db.execSQL("CREATE INDEX IF NOT EXISTS range_sample_session ON range_samples(session_id,sampled_at)");
db.execSQL("CREATE TABLE IF NOT EXISTS vendor_lookup (lookup_type TEXT,hex_code TEXT," +
"vendor_name TEXT,PRIMARY KEY(lookup_type,hex_code))");
db.execSQL("CREATE TABLE IF NOT EXISTS device_observations (id INTEGER PRIMARY KEY AUTOINCREMENT," +
"address TEXT,first_seen_at INTEGER,last_seen_at INTEGER,rssi INTEGER,strongest_rssi INTEGER," +
"sighting_count INTEGER DEFAULT 1,broadcast_name TEXT,vendor_name TEXT,device_category TEXT," +
"broadcast_type TEXT,appearance INTEGER DEFAULT -1,company_id INTEGER DEFAULT -1," +
"advertisement_hex TEXT,latitude REAL,longitude REAL,accuracy REAL,has_location INTEGER DEFAULT 0," +
"uploaded INTEGER DEFAULT 0)");
db.execSQL("CREATE INDEX IF NOT EXISTS observation_address_time ON device_observations(address,last_seen_at)");
db.execSQL("CREATE INDEX IF NOT EXISTS observation_upload ON device_observations(uploaded,last_seen_at)");
}
private void seedVendors(SQLiteDatabase db) {
addVendor(db, "OUI", "A4C3F0", "Intel Corporate");
addVendor(db, "OUI", "0000F0", "Samsung Electronics");
addVendor(db, "OUI", "0452C7", "Bose Corporation");
addVendor(db, "OUI", "C4E510", "Tile, Inc.");
addVendor(db, "OUI", "001A80", "Sony Corporation");
addVendor(db, "OUI", "5882A8", "Xiaomi Communications");
addVendor(db, "OUI", "F4F5D8", "Google LLC");
addVendor(db, "COMPANY", "0006", "Microsoft Corporation");
addVendor(db, "COMPANY", "0059", "Nordic Semiconductor ASA");
addVendor(db, "COMPANY", "0075", "Samsung Electronics");
addVendor(db, "COMPANY", "009E", "Bose Corporation");
addVendor(db, "COMPANY", "00E0", "Google LLC");
addVendor(db, "COMPANY", "018E", "Fitbit, Inc.");
addVendor(db, "COMPANY", "067C", "Tile, Inc.");
addVendor(db, "COMPANY", "0171", "Amazon.com Services LLC");
addVendor(db, "COMPANY", "00D0", "Dexcom, Inc.");
addVendor(db, "COMPANY", "0131", "Cypress Semiconductor");
}
private void addVendor(SQLiteDatabase db, String type, String code, String vendor) {
ContentValues values = new ContentValues();
values.put("lookup_type", type);
values.put("hex_code", code);
values.put("vendor_name", vendor);
db.insertWithOnConflict("vendor_lookup", null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
public synchronized void connected(String address, String broadcast, long time, Fix fix) {
DeviceRecord old = get(address);
ContentValues values = base(address, broadcast, time, fix);
values.put("connected", 1);
values.put("connected_at", time);
values.put("disconnected_at", 0);
values.put("reason", "Connected");
values.put("is_tracked", 1);
if (old == null) values.put("pin_color", colorFor(address));
if (old != null) preserveProfile(values, old, fix);
getWritableDatabase().insertWithOnConflict("devices", null, values,
SQLiteDatabase.CONFLICT_REPLACE);
if (fix != null) insertHistory(address, fix, time);
}
public synchronized void disconnected(String address, String broadcast, long time,
Fix fix, String reason) {
ContentValues values = base(address, broadcast, time, fix);
values.put("connected", 0);
values.put("disconnected_at", time);
values.put("reason", reason);
values.put("is_tracked", 1);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
if (fix != null) insertHistory(address, fix, time);
}
public synchronized void telemetry(String address, String broadcast, Integer battery,
Integer rssi, long time, Fix fix) {
DeviceRecord old = get(address);
ContentValues values = base(address, broadcast, time, fix);
if (battery != null) values.put("battery", battery.intValue());
if (rssi != null) { values.put("rssi", rssi.intValue()); values.put("last_rssi_at", time); }
if (old == null) {
values.put("connected", 0);
getWritableDatabase().insert("devices", null, values);
} else {
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
}
public synchronized void recordObservation(String address, long time, int rssi,
String broadcastName, String vendorName,
String category, String broadcastType,
int appearance, int companyId, String advertisementHex,
Fix fix) {
if (address == null || address.length() == 0 || rssi == 0 || rssi == 127) return;
SQLiteDatabase db = getWritableDatabase();
Cursor cursor = db.query("device_observations",
new String[] { "id", "last_seen_at", "strongest_rssi", "sighting_count" },
"address=?", new String[] { address }, null, null, "last_seen_at DESC", "1");
long id = -1L; long last = 0L; int strongest = rssi; int count = 0;
try {
if (cursor.moveToFirst()) {
id = cursor.getLong(0); last = cursor.getLong(1);
strongest = Math.max(rssi, cursor.getInt(2)); count = cursor.getInt(3);
}
} finally { cursor.close(); }
ContentValues values = new ContentValues();
values.put("address", address); values.put("last_seen_at", time); values.put("rssi", rssi);
values.put("strongest_rssi", strongest);
if (broadcastName != null) values.put("broadcast_name", broadcastName);
if (vendorName != null) values.put("vendor_name", vendorName);
if (category != null) values.put("device_category", category);
if (broadcastType != null) values.put("broadcast_type", broadcastType);
values.put("appearance", appearance); values.put("company_id", companyId);
if (advertisementHex != null) values.put("advertisement_hex", advertisementHex);
if (fix != null) {
values.put("latitude", fix.latitude); values.put("longitude", fix.longitude);
values.put("accuracy", fix.accuracy); values.put("has_location", 1);
}
if (id >= 0L && time - last <= 15000L) {
values.put("sighting_count", count + 1);
db.update("device_observations", values, "id=?", new String[] { String.valueOf(id) });
} else {
values.put("first_seen_at", time); values.put("sighting_count", 1); values.put("uploaded", 0);
db.insert("device_observations", null, values);
}
}
public synchronized void updateLocation(String address, Fix fix, long time) {
if (fix == null) return;
ContentValues values = new ContentValues();
values.put("latitude", fix.latitude);
values.put("longitude", fix.longitude);
values.put("accuracy", fix.accuracy);
values.put("has_location", 1);
values.put("updated_at", time);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
insertHistory(address, fix, time);
}
private void insertHistory(String address, Fix fix, long time) {
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);
getWritableDatabase().insert("location_history", null, values);
}
public synchronized void updateProfile(String address, String title, String photo,
String icon) {
ContentValues values = new ContentValues();
if (title != null && title.trim().length() > 0) values.put("custom_name", title.trim());
values.put("photo_uri", photo);
values.put("photo_path", photo);
values.put("icon_preset", icon);
values.put("is_tracked", 1);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized void updateCalibration(String address, int txPower, double exponent) {
ContentValues values = new ContentValues();
values.put("tx_power_1m", txPower);
values.put("path_loss_exponent", exponent);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized void logRangeSample(String address, String sessionId, long time,
double latitude, double longitude, float accuracy,
int rawRssi, double filteredRssi, float heading,
String state) {
ContentValues values = new ContentValues();
values.put("address", address); values.put("session_id", sessionId);
values.put("sampled_at", time); values.put("latitude", latitude);
values.put("longitude", longitude); values.put("accuracy", accuracy);
values.put("raw_rssi", rawRssi); values.put("filtered_rssi", filteredRssi);
values.put("heading", heading); values.put("range_state", state);
getWritableDatabase().insert("range_samples", null, values);
}
public synchronized void updateTracking(String address, int minutes, String color) {
ContentValues values = new ContentValues();
values.put("tracking_priority", minutes);
values.put("device_gps_interval", minutes);
values.put("gps_polling_rate", minutes == 0 ? 0 : minutes * 60000);
values.put("pin_color", color);
values.put("is_tracked", 1);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized void markTracked(String address, String broadcast) {
DeviceRecord old = get(address);
if (old == null) {
ContentValues created = base(address, broadcast, 0L, null);
created.put("connected", 0);
created.put("is_tracked", 1);
created.put("pin_color", colorFor(address));
getWritableDatabase().insert("devices", null, created);
return;
}
ContentValues values = new ContentValues();
values.put("is_tracked", 1);
if (old.tracked == 0) values.put("pin_color", colorFor(address));
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized void removeFromTracking(String address) {
ContentValues values = new ContentValues();
values.put("is_tracked", 0);
values.put("map_enabled", 0);
values.put("live_enabled", 0);
values.put("track_broadcast", 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 });
}
public synchronized void setLiveEnabled(String address, boolean enabled) {
ContentValues values = new ContentValues(); values.put("live_enabled", enabled ? 1 : 0);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized void setTrackBroadcast(String address, boolean enabled) {
ContentValues values = new ContentValues(); values.put("track_broadcast", enabled ? 1 : 0);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized void setShowTrail(String address, boolean shown) {
ContentValues values = new ContentValues(); values.put("show_trail", shown ? 1 : 0);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized void setLocationLocked(String address, boolean locked) {
ContentValues values = new ContentValues(); values.put("location_locked", locked ? 1 : 0);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized void setCustomName(String address, String name) {
ContentValues values = new ContentValues();
values.put("custom_name", name == null ? null : name.trim());
values.put("is_tracked", 1);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized void setDetectedCategory(String address, String category) {
DeviceRecord record = get(address);
if (record != null && record.categoryManual == 1) return;
ContentValues values = new ContentValues();
values.put("device_category", category);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized void setCategory(String address, String category) {
if (category == null) return;
ContentValues values = new ContentValues();
values.put("device_category", category);
values.put("category_manual", 1);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized void setVendor(String address, String vendor) {
DeviceRecord old = get(address);
if (old != null && old.vendorManual == 1) return;
if (vendor == null) return;
ContentValues values = new ContentValues();
values.put("vendor_name", vendor);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized void setManualVendor(String address, String vendor) {
ContentValues values = new ContentValues();
values.put("vendor_name", vendor == null ? null : vendor.trim());
values.put("vendor_manual", 1);
getWritableDatabase().update("devices", values, "address=?", new String[] { address });
}
public synchronized String lookupOui(String address) {
if (address == null) return null;
String code = address.replace(":", "").replace("-", "").toUpperCase(Locale.US);
if (code.length() < 6) return null;
return lookupVendor("OUI", code.substring(0, 6));
}
public synchronized String lookupCompany(int companyId) {
return lookupVendor("COMPANY", String.format(Locale.US, "%04X", companyId & 0xffff));
}
private String lookupVendor(String type, String code) {
Cursor cursor = getReadableDatabase().query("vendor_lookup",
new String[] { "vendor_name" }, "lookup_type=? AND hex_code=?",
new String[] { type, code }, null, null, null);
try { return cursor.moveToFirst() ? cursor.getString(0) : null; }
finally { cursor.close(); }
}
public synchronized DeviceRecord get(String address) {
Cursor cursor = getReadableDatabase().query("devices", null, "address=?",
new String[] { address }, null, null, null);
try { return cursor.moveToFirst() ? read(cursor) : null; }
finally { cursor.close(); }
}
public synchronized List<DeviceRecord> all() { return queryDevices(null, null); }
public synchronized List<DeviceRecord> tracked() {
return queryDevices("is_tracked=1", null);
}
private List<DeviceRecord> queryDevices(String where, String[] args) {
ArrayList<DeviceRecord> result = new ArrayList<DeviceRecord>();
Cursor cursor = getReadableDatabase().query("devices", null, where, args,
null, null, "updated_at DESC");
try { while (cursor.moveToNext()) result.add(read(cursor)); }
finally { cursor.close(); }
return result;
}
public synchronized List<LocationPoint> history(String address, long since) {
ArrayList<LocationPoint> result = new ArrayList<LocationPoint>();
Cursor cursor = getReadableDatabase().query("location_history", null,
"address=? AND logged_at>=?", new String[] { address, String.valueOf(since) },
null, null, "logged_at ASC");
try {
while (cursor.moveToNext()) {
LocationPoint point = new LocationPoint();
point.latitude = cursor.getDouble(cursor.getColumnIndex("latitude"));
point.longitude = cursor.getDouble(cursor.getColumnIndex("longitude"));
point.accuracy = cursor.getFloat(cursor.getColumnIndex("accuracy"));
point.timestamp = cursor.getLong(cursor.getColumnIndex("logged_at"));
result.add(point);
}
} finally { cursor.close(); }
return result;
}
private ContentValues base(String address, String broadcast, long time, Fix fix) {
ContentValues values = new ContentValues();
values.put("address", address);
if (meaningfulBroadcast(broadcast)) {
values.put("name", broadcast.trim());
values.put("broadcast_name", broadcast.trim());
}
values.put("updated_at", time);
if (fix != null) {
values.put("latitude", fix.latitude);
values.put("longitude", fix.longitude);
values.put("accuracy", fix.accuracy);
values.put("has_location", 1);
}
return values;
}
private boolean meaningfulBroadcast(String value) {
return value != null && value.trim().length() > 0
&& !"Unknown Device".equalsIgnoreCase(value.trim())
&& !"Unknown item".equalsIgnoreCase(value.trim());
}
private void preserveProfile(ContentValues values, DeviceRecord old, Fix fix) {
values.put("battery", old.battery);
values.put("rssi", old.rssi);
values.put("last_rssi_at", old.lastRssiAt);
values.put("photo_uri", old.photoUri);
values.put("photo_path", old.photoUri);
values.put("icon_preset", old.iconPreset);
values.put("pin_color", old.pinColor);
values.put("tracking_priority", old.trackingPriority);
values.put("device_gps_interval", old.trackingPriority);
values.put("gps_polling_rate", old.gpsPollingRate);
values.put("track_broadcast", old.trackBroadcast);
values.put("custom_name", old.customName);
values.put("vendor_name", old.vendorName);
values.put("tx_power_1m", old.txPowerAtOneMeter);
values.put("path_loss_exponent", old.pathLossExponent);
if (fix == null && old.hasLocation) {
values.put("latitude", old.latitude);
values.put("longitude", old.longitude);
values.put("accuracy", old.accuracy);
values.put("has_location", 1);
}
}
private DeviceRecord read(Cursor c) {
DeviceRecord r = new DeviceRecord();
r.address = string(c, "address");
r.name = string(c, "name");
r.customName = string(c, "custom_name");
r.broadcastName = string(c, "broadcast_name");
r.vendorName = string(c, "vendor_name");
r.category = string(c, "device_category");
if (r.category == null) r.category = "Other";
r.categoryManual = integer(c, "category_manual");
r.vendorManual = integer(c, "vendor_manual");
r.mapEnabled = integer(c, "map_enabled");
r.liveEnabled = integer(c, "live_enabled");
r.locationLocked = integer(c, "location_locked");
r.showTrail = integer(c, "show_trail");
r.trackBroadcast = integer(c, "track_broadcast");
r.connected = integer(c, "connected");
r.connectedAt = number(c, "connected_at");
r.disconnectedAt = number(c, "disconnected_at");
r.latitude = decimal(c, "latitude");
r.longitude = decimal(c, "longitude");
r.accuracy = (float) decimal(c, "accuracy");
r.hasLocation = integer(c, "has_location") == 1;
r.battery = integer(c, "battery");
r.rssi = integer(c, "rssi");
r.lastRssiAt = number(c, "last_rssi_at");
r.reason = string(c, "reason");
r.updatedAt = number(c, "updated_at");
r.photoUri = string(c, "photo_path");
if (r.photoUri == null) r.photoUri = string(c, "photo_uri");
r.iconPreset = string(c, "icon_preset");
r.pinColor = string(c, "pin_color");
if (r.pinColor == null) r.pinColor = "#E53935";
r.trackingPriority = integer(c, "device_gps_interval");
r.gpsPollingRate = r.trackingPriority == 0 ? 0 : r.trackingPriority * 60000;
r.tracked = integer(c, "is_tracked");
r.txPowerAtOneMeter = integer(c, "tx_power_1m");
if (r.txPowerAtOneMeter == 0) r.txPowerAtOneMeter = -59;
r.pathLossExponent = decimal(c, "path_loss_exponent");
if (r.pathLossExponent <= 0) r.pathLossExponent = 2.7;
return r;
}
private int column(Cursor c, String name) { return c.getColumnIndex(name); }
private String string(Cursor c, String name) {
int index = column(c, name);
return index < 0 || c.isNull(index) ? null : c.getString(index);
}
private int integer(Cursor c, String name) {
int index = column(c, name);
return index < 0 || c.isNull(index) ? 0 : c.getInt(index);
}
private long number(Cursor c, String name) {
int index = column(c, name);
return index < 0 || c.isNull(index) ? 0 : c.getLong(index);
}
private double decimal(Cursor c, String name) {
int index = column(c, name);
return index < 0 || c.isNull(index) ? 0 : c.getDouble(index);
}
private String colorFor(String address) {
String[] colors = { "#E53935", "#1E88E5", "#43A047", "#FB8C00", "#8E24AA", "#00897B" };
int hash = address == null ? 0 : address.hashCode();
if (hash == Integer.MIN_VALUE) hash = 0;
return colors[Math.abs(hash) % colors.length];
}
private String safe(String value) {
return value == null || value.length() == 0 ? "Unknown item" : value;
}
public static class Fix {
public double latitude;
public double longitude;
public float accuracy;
public Fix(double lat, double lon, float acc) {
latitude = lat; longitude = lon; accuracy = acc;
}
}
}