diff --git a/api.properties b/api.properties index 8ff1ef70..dbb49c29 100644 --- a/api.properties +++ b/api.properties @@ -1,2 +1,2 @@ -API_KEY="XcEJDaf0.iGWKpSJUiGN7z2Z7a0aAJkhUBJSYN9xP" +API_KEY="R8ZwC7u3.3TkBKfHkHhS590nO6VlbtOG5RLTbO0r5" API_BASE_ADDRESS="https://tpe.seemoo.tu-darmstadt.de/api/" \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e144135e..6071c5c5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { applicationId "de.seemoo.at_tracking_detection" minSdkVersion 21 targetSdkVersion 31 - versionCode 23 - versionName "1.2" + versionCode 29 + versionName "1.3.2" buildConfigField "String", "API_KEY", apiProperties["API_KEY"] buildConfigField "String", "API_BASE_ADDRESS", apiProperties["API_BASE_ADDRESS"] @@ -61,6 +61,13 @@ android { kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } + + sourceSets { + // Adds exported schema location as test app assets. + androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) +// androidTest.assets.srcDirs += files("/Users/seemoo/Work/Research/AirGuard/airguard-app/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase".toString()) + } + } aboutLibraries { @@ -75,7 +82,7 @@ dependencies { implementation 'androidx.work:work-runtime-ktx:2.7.1' implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.6.0-alpha03' + implementation 'com.google.android.material:material:1.6.0-beta01' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.vectordrawable:vectordrawable:1.1.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1' @@ -115,6 +122,9 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation "androidx.room:room-testing:2.4.2" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' } \ No newline at end of file diff --git a/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/5.json b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/5.json new file mode 100644 index 00000000..be90a8ca --- /dev/null +++ b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/5.json @@ -0,0 +1,256 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "b3de17964491408ab3b601a6554975de", + "entities": [ + { + "tableName": "device", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uniqueId` TEXT, `address` TEXT NOT NULL, `name` TEXT, `ignore` INTEGER NOT NULL, `connectable` INTEGER DEFAULT 0, `payloadData` INTEGER, `firstDiscovery` TEXT NOT NULL, `lastSeen` TEXT NOT NULL, `notificationSent` INTEGER NOT NULL, `lastNotificationSent` TEXT, `deviceType` TEXT DEFAULT null)", + "fields": [ + { + "fieldPath": "deviceId", + "columnName": "deviceId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uniqueId", + "columnName": "uniqueId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "ignore", + "columnName": "ignore", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "connectable", + "columnName": "connectable", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "0" + }, + { + "fieldPath": "payloadData", + "columnName": "payloadData", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "firstDiscovery", + "columnName": "firstDiscovery", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastSeen", + "columnName": "lastSeen", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationSent", + "columnName": "notificationSent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastNotificationSent", + "columnName": "lastNotificationSent", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deviceType", + "columnName": "deviceType", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "null" + } + ], + "primaryKey": { + "columnNames": [ + "deviceId" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_device_address", + "unique": true, + "columnNames": [ + "address" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_device_address` ON `${TABLE_NAME}` (`address`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`notificationId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `deviceAddress` TEXT NOT NULL, `falseAlarm` INTEGER NOT NULL, `dismissed` INTEGER, `clicked` INTEGER, `createdAt` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "notificationId", + "columnName": "notificationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceAddress", + "columnName": "deviceAddress", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "falseAlarm", + "columnName": "falseAlarm", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dismissed", + "columnName": "dismissed", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "clicked", + "columnName": "clicked", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "notificationId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "beacon", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`beaconId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `receivedAt` TEXT NOT NULL, `rssi` INTEGER NOT NULL, `deviceAddress` TEXT NOT NULL, `longitude` REAL, `latitude` REAL, `mfg` BLOB, `serviceUUIDs` TEXT)", + "fields": [ + { + "fieldPath": "beaconId", + "columnName": "beaconId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "receivedAt", + "columnName": "receivedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rssi", + "columnName": "rssi", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceAddress", + "columnName": "deviceAddress", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "longitude", + "columnName": "longitude", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "latitude", + "columnName": "latitude", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "manufacturerData", + "columnName": "mfg", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "serviceUUIDs", + "columnName": "serviceUUIDs", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "beaconId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "feedback", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`feedbackId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `notificationId` INTEGER NOT NULL, `location` TEXT)", + "fields": [ + { + "fieldPath": "feedbackId", + "columnName": "feedbackId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationId", + "columnName": "notificationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "location", + "columnName": "location", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "feedbackId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b3de17964491408ab3b601a6554975de')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/6.json b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/6.json new file mode 100644 index 00000000..9f411780 --- /dev/null +++ b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/6.json @@ -0,0 +1,255 @@ +{ + "formatVersion": 1, + "database": { + "version": 6, + "identityHash": "6766e734817eb1fa11f5d301ba67070c", + "entities": [ + { + "tableName": "device", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uniqueId` TEXT, `address` TEXT NOT NULL, `name` TEXT, `ignore` INTEGER NOT NULL, `connectable` INTEGER DEFAULT 0, `payloadData` INTEGER, `firstDiscovery` TEXT NOT NULL, `lastSeen` TEXT NOT NULL, `notificationSent` INTEGER NOT NULL, `lastNotificationSent` TEXT, `deviceType` TEXT)", + "fields": [ + { + "fieldPath": "deviceId", + "columnName": "deviceId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uniqueId", + "columnName": "uniqueId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "ignore", + "columnName": "ignore", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "connectable", + "columnName": "connectable", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "0" + }, + { + "fieldPath": "payloadData", + "columnName": "payloadData", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "firstDiscovery", + "columnName": "firstDiscovery", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastSeen", + "columnName": "lastSeen", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationSent", + "columnName": "notificationSent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastNotificationSent", + "columnName": "lastNotificationSent", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deviceType", + "columnName": "deviceType", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "deviceId" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_device_address", + "unique": true, + "columnNames": [ + "address" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_device_address` ON `${TABLE_NAME}` (`address`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`notificationId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `deviceAddress` TEXT NOT NULL, `falseAlarm` INTEGER NOT NULL, `dismissed` INTEGER, `clicked` INTEGER, `createdAt` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "notificationId", + "columnName": "notificationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceAddress", + "columnName": "deviceAddress", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "falseAlarm", + "columnName": "falseAlarm", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dismissed", + "columnName": "dismissed", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "clicked", + "columnName": "clicked", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "notificationId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "beacon", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`beaconId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `receivedAt` TEXT NOT NULL, `rssi` INTEGER NOT NULL, `deviceAddress` TEXT NOT NULL, `longitude` REAL, `latitude` REAL, `mfg` BLOB, `serviceUUIDs` TEXT)", + "fields": [ + { + "fieldPath": "beaconId", + "columnName": "beaconId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "receivedAt", + "columnName": "receivedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rssi", + "columnName": "rssi", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceAddress", + "columnName": "deviceAddress", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "longitude", + "columnName": "longitude", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "latitude", + "columnName": "latitude", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "manufacturerData", + "columnName": "mfg", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "serviceUUIDs", + "columnName": "serviceUUIDs", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "beaconId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "feedback", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`feedbackId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `notificationId` INTEGER NOT NULL, `location` TEXT)", + "fields": [ + { + "fieldPath": "feedbackId", + "columnName": "feedbackId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationId", + "columnName": "notificationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "location", + "columnName": "location", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "feedbackId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6766e734817eb1fa11f5d301ba67070c')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/7.json b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/7.json new file mode 100644 index 00000000..d69580f6 --- /dev/null +++ b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/7.json @@ -0,0 +1,255 @@ +{ + "formatVersion": 1, + "database": { + "version": 7, + "identityHash": "6766e734817eb1fa11f5d301ba67070c", + "entities": [ + { + "tableName": "device", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uniqueId` TEXT, `address` TEXT NOT NULL, `name` TEXT, `ignore` INTEGER NOT NULL, `connectable` INTEGER DEFAULT 0, `payloadData` INTEGER, `firstDiscovery` TEXT NOT NULL, `lastSeen` TEXT NOT NULL, `notificationSent` INTEGER NOT NULL, `lastNotificationSent` TEXT, `deviceType` TEXT)", + "fields": [ + { + "fieldPath": "deviceId", + "columnName": "deviceId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uniqueId", + "columnName": "uniqueId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "ignore", + "columnName": "ignore", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "connectable", + "columnName": "connectable", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "0" + }, + { + "fieldPath": "payloadData", + "columnName": "payloadData", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "firstDiscovery", + "columnName": "firstDiscovery", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastSeen", + "columnName": "lastSeen", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationSent", + "columnName": "notificationSent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastNotificationSent", + "columnName": "lastNotificationSent", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deviceType", + "columnName": "deviceType", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "deviceId" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_device_address", + "unique": true, + "columnNames": [ + "address" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_device_address` ON `${TABLE_NAME}` (`address`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`notificationId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `deviceAddress` TEXT NOT NULL, `falseAlarm` INTEGER NOT NULL, `dismissed` INTEGER, `clicked` INTEGER, `createdAt` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "notificationId", + "columnName": "notificationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceAddress", + "columnName": "deviceAddress", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "falseAlarm", + "columnName": "falseAlarm", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dismissed", + "columnName": "dismissed", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "clicked", + "columnName": "clicked", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "notificationId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "beacon", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`beaconId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `receivedAt` TEXT NOT NULL, `rssi` INTEGER NOT NULL, `deviceAddress` TEXT NOT NULL, `longitude` REAL, `latitude` REAL, `mfg` BLOB, `serviceUUIDs` TEXT)", + "fields": [ + { + "fieldPath": "beaconId", + "columnName": "beaconId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "receivedAt", + "columnName": "receivedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rssi", + "columnName": "rssi", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceAddress", + "columnName": "deviceAddress", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "longitude", + "columnName": "longitude", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "latitude", + "columnName": "latitude", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "manufacturerData", + "columnName": "mfg", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "serviceUUIDs", + "columnName": "serviceUUIDs", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "beaconId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "feedback", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`feedbackId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `notificationId` INTEGER NOT NULL, `location` TEXT)", + "fields": [ + { + "fieldPath": "feedbackId", + "columnName": "feedbackId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationId", + "columnName": "notificationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "location", + "columnName": "location", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "feedbackId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6766e734817eb1fa11f5d301ba67070c')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/de/seemoo/at_tracking_detection/ExampleInstrumentedTest.kt b/app/src/androidTest/java/de/seemoo/at_tracking_detection/ExampleInstrumentedTest.kt index a3063a9e..93e2b657 100644 --- a/app/src/androidTest/java/de/seemoo/at_tracking_detection/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/de/seemoo/at_tracking_detection/ExampleInstrumentedTest.kt @@ -60,7 +60,8 @@ class ExampleInstrumentedTest { deviceAddress = "00:00:00:00:00", longitude = 8.24823948, latitude = 51.4839483, - mfg = null + mfg = null, + serviceUUIDs = null ), Beacon( receivedAt = LocalDateTime.of(2021, 11, 20, 10, 30), @@ -68,7 +69,8 @@ class ExampleInstrumentedTest { deviceAddress = "00:00:00:00:00", longitude = 8.24823948, latitude = 51.4839483, - mfg = null + mfg = null, + serviceUUIDs = null ) ) @@ -79,7 +81,8 @@ class ExampleInstrumentedTest { deviceAddress = "00:00:00:00:00", longitude = 8.24823948, latitude = 51.4839483, - mfg = null + mfg = null, + serviceUUIDs = null ), Beacon( receivedAt = LocalDateTime.of(2021, 11, 20, 10, 45), @@ -87,7 +90,8 @@ class ExampleInstrumentedTest { deviceAddress = "00:00:00:00:00", longitude = 8.24823948, latitude = 51.4839483, - mfg = null + mfg = null, + serviceUUIDs = null ) ) @@ -98,7 +102,8 @@ class ExampleInstrumentedTest { deviceAddress = "00:00:00:00:00", longitude = 8.24823948, latitude = 51.4839483, - mfg = null + mfg = null, + serviceUUIDs = null ), Beacon( receivedAt = LocalDateTime.of(2021, 11, 22, 10, 45), @@ -106,7 +111,8 @@ class ExampleInstrumentedTest { deviceAddress = "00:00:00:00:00", longitude = 8.24823948, latitude = 51.4839483, - mfg = null + mfg = null, + serviceUUIDs = null ) ) @@ -117,7 +123,8 @@ class ExampleInstrumentedTest { deviceAddress = "00:00:00:00:00", longitude = 8.24823948, latitude = 51.4839483, - mfg = null + mfg = null, + serviceUUIDs = null ), Beacon( receivedAt = LocalDateTime.of(2021, 11, 20, 10, 0), @@ -125,7 +132,8 @@ class ExampleInstrumentedTest { deviceAddress = "00:00:00:00:00", longitude = 8.24823948, latitude = 51.4839483, - mfg = null + mfg = null, + serviceUUIDs = null ) ) @@ -136,7 +144,8 @@ class ExampleInstrumentedTest { deviceAddress = "00:00:00:00:00", longitude = 8.24823948, latitude = 51.4839483, - mfg = null + mfg = null, + serviceUUIDs = null ), Beacon( receivedAt = LocalDateTime.of(2021, 11, 20, 10, 29), @@ -144,7 +153,8 @@ class ExampleInstrumentedTest { deviceAddress = "00:00:00:00:00", longitude = 8.24823948, latitude = 51.4839483, - mfg = null + mfg = null, + serviceUUIDs = null ) ) @@ -155,7 +165,8 @@ class ExampleInstrumentedTest { deviceAddress = "00:00:00:00:00", longitude = 8.24823948, latitude = 51.4839483, - mfg = null + mfg = null, + serviceUUIDs = null ), Beacon( receivedAt = LocalDateTime.of(2021, 11, 20, 10, 20), @@ -163,7 +174,8 @@ class ExampleInstrumentedTest { deviceAddress = "00:00:00:00:00", longitude = 8.24823948, latitude = 51.4839483, - mfg = null + mfg = null, + serviceUUIDs = null ) ) diff --git a/app/src/androidTest/java/de/seemoo/at_tracking_detection/MigrationTest.kt b/app/src/androidTest/java/de/seemoo/at_tracking_detection/MigrationTest.kt new file mode 100644 index 00000000..1ed6c8df --- /dev/null +++ b/app/src/androidTest/java/de/seemoo/at_tracking_detection/MigrationTest.kt @@ -0,0 +1,234 @@ +package de.seemoo.at_tracking_detection + +import androidx.room.Room +import androidx.room.migration.Migration +import androidx.room.testing.MigrationTestHelper +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import de.seemoo.at_tracking_detection.database.AppDatabase +import de.seemoo.at_tracking_detection.database.daos.BeaconDao_Impl +import de.seemoo.at_tracking_detection.database.repository.BeaconRepository +import de.seemoo.at_tracking_detection.hilt.DatabaseModule +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.io.IOException +import java.time.LocalDateTime + +@RunWith(AndroidJUnit4::class) +class MigrationTest { + private val TEST_DB = "migration-test" + + private val AUTO_MIGGRAITON_2_3 = object : Migration(2, 3) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE `device` ADD COLUMN `name` TEXT DEFAULT NULL") + } + } + + private val AUTO_MIGRATION_3_4 = object : Migration(3, 4) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE `notification` ADD COLUMN `dismissed` INTEGER DEFAULT NULL") + database.execSQL("ALTER TABLE `notification` ADD COLUMN `clicked` INTEGER DEFAULT NULL") + database.execSQL("CREATE TABLE IF NOT EXISTS `_new_device` (`deviceId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uniqueId` TEXT, `address` TEXT NOT NULL, `name` TEXT, `ignore` INTEGER NOT NULL, `connectable` INTEGER, `payloadData` INTEGER, `firstDiscovery` TEXT NOT NULL, `lastSeen` TEXT NOT NULL, `notificationSent` INTEGER NOT NULL, `lastNotificationSent` TEXT, `deviceType` TEXT)") + database.execSQL("INSERT INTO `_new_device` (`lastNotificationSent`,`address`,`connectable`,`firstDiscovery`,`lastSeen`,`name`,`ignore`,`payloadData`,`deviceId`,`notificationSent`) SELECT `lastNotificationSent`,`address`,`connectable`,`firstDiscovery`,`lastSeen`,`name`,`ignore`,`payloadData`,`deviceId`,`notificationSent` FROM `device`") + database.execSQL("DROP TABLE `device`") + database.execSQL("ALTER TABLE `_new_device` RENAME TO `device`") + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_device_address` ON `device` (`address`)") + } + } + + private val AUTO_MIGRATION_4_5 = object : Migration(4, 5) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE `beacon` ADD COLUMN `serviceUUIDs` TEXT DEFAULT NULL") + database.execSQL("CREATE TABLE IF NOT EXISTS `_new_device` (`deviceId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uniqueId` TEXT, `address` TEXT NOT NULL, `name` TEXT, `ignore` INTEGER NOT NULL, `connectable` INTEGER DEFAULT 0, `payloadData` INTEGER, `firstDiscovery` TEXT NOT NULL, `lastSeen` TEXT NOT NULL, `notificationSent` INTEGER NOT NULL, `lastNotificationSent` TEXT, `deviceType` TEXT DEFAULT null)") + database.execSQL("INSERT INTO `_new_device` (`deviceType`,`lastNotificationSent`,`address`,`connectable`,`firstDiscovery`,`lastSeen`,`name`,`ignore`,`payloadData`,`deviceId`,`uniqueId`,`notificationSent`) SELECT `deviceType`,`lastNotificationSent`,`address`,`connectable`,`firstDiscovery`,`lastSeen`,`name`,`ignore`,`payloadData`,`deviceId`,`uniqueId`,`notificationSent` FROM `device`") + database.execSQL("DROP TABLE `device`") + database.execSQL("ALTER TABLE `_new_device` RENAME TO `device`") + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_device_address` ON `device` (`address`)") + } + } + + private val AUTO_MIGRATION_5_6 = object : Migration(5, 6) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE IF NOT EXISTS `_new_device` (`deviceId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uniqueId` TEXT, `address` TEXT NOT NULL, `name` TEXT, `ignore` INTEGER NOT NULL, `connectable` INTEGER DEFAULT 0, `payloadData` INTEGER, `firstDiscovery` TEXT NOT NULL, `lastSeen` TEXT NOT NULL, `notificationSent` INTEGER NOT NULL, `lastNotificationSent` TEXT, `deviceType` TEXT)") + database.execSQL("INSERT INTO `_new_device` (`deviceType`,`lastNotificationSent`,`address`,`connectable`,`firstDiscovery`,`lastSeen`,`name`,`ignore`,`payloadData`,`deviceId`,`uniqueId`,`notificationSent`) SELECT `deviceType`,`lastNotificationSent`,`address`,`connectable`,`firstDiscovery`,`lastSeen`,`name`,`ignore`,`payloadData`,`deviceId`,`uniqueId`,`notificationSent` FROM `device`") + database.execSQL("DROP TABLE `device`") + database.execSQL("ALTER TABLE `_new_device` RENAME TO `device`") + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_device_address` ON `device` (`address`)") + } + } + + + private val ALL_MIGRATIONS = arrayOf( + DatabaseModule.MIGRATION_5_7, DatabaseModule.MIGRATION_6_7 + ) + + @get:Rule + val helper: MigrationTestHelper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + AppDatabase::class.java.canonicalName, + FrameworkSQLiteOpenHelperFactory() + ) + + fun insertDummyData(db: SupportSQLiteDatabase) { + db.execSQL("INSERT INTO `beacon` (`receivedAt`, `rssi`, `deviceAddress`, `longitude`, `latitude`) VALUES ('2022-03-25T10:00:00', -78, 'aa:bb:cc:dd:ee', 50.04231, 8.34423)") + db.execSQL("INSERT INTO `device` (`address` , `ignore`, `connectable`, `firstDiscovery`, `lastSeen`, `notificationSent`) VALUES ('aa:bb:cc:dd:ee', false, true, '2022-03-25', '2022-03-25T10:00:00', false)") + } + + fun insertNewBeacon(db: SupportSQLiteDatabase) { + db.execSQL("INSERT INTO `beacon` (`receivedAt`, `rssi`, `deviceAddress`, `longitude`, `latitude`, `serviceUUIDs`) VALUES ('2022-03-25T11:00:00', -78, 'aa:bb:cc:dd:ee', 50.04231, 8.34423, 'AA:BB;DD:EE;FF:EE')") + } + + fun assertDBMigrations(roomDB: AppDatabase) { + val time = LocalDateTime.of(2022,3,1,0,0,0,0) + val beaconDao = BeaconDao_Impl(roomDB) + val beaconRepository = BeaconRepository(beaconDao) + val beacons = beaconRepository.getLatestBeacons(time) + val serviceBeacon = beacons.filter { it.serviceUUIDs != null }.first() + assert(serviceBeacon.serviceUUIDs != null) + assert(serviceBeacon.serviceUUIDs == arrayListOf("AA:BB","DD:EE","FF:EE")) + + val otherBeacon = beacons.first { it.serviceUUIDs == null } + assert(otherBeacon.serviceUUIDs == null) + } + + @Test + @Throws(IOException::class) + fun migrateAll() { + + helper.createDatabase(TEST_DB, 2).apply { + insertDummyData(this) + close() + } + + val roomDB = Room.databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + AppDatabase::class.java, + TEST_DB + ).addMigrations(*ALL_MIGRATIONS).build().apply { + insertNewBeacon(openHelper.writableDatabase) + openHelper.writableDatabase.close() + } + + assertDBMigrations(roomDB) + } + + @Test + @Throws(IOException::class) + fun roomMigrate3_7() { + helper.createDatabase(TEST_DB, 3).apply { + insertDummyData(this) + close() + } + + val roomDB = Room.databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + AppDatabase::class.java, + TEST_DB + ).addMigrations(*ALL_MIGRATIONS).build().apply { + insertNewBeacon(openHelper.writableDatabase) + openHelper.writableDatabase.close() + } + + assertDBMigrations(roomDB) + } + + @Test + @Throws(IOException::class) + fun roomMigrate4_7() { + helper.createDatabase(TEST_DB, 4).apply { + insertDummyData(this) + close() + } + + val roomDB = Room.databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + AppDatabase::class.java, + TEST_DB + ).addMigrations(*ALL_MIGRATIONS).build().apply { + insertNewBeacon(openHelper.writableDatabase) + openHelper.writableDatabase.close() + } + + assertDBMigrations(roomDB) + } + + @Test + @Throws(IOException::class) + fun roomMigrate5_7() { + helper.createDatabase(TEST_DB, 5).apply { + insertDummyData(this) + close() + } + + val roomDB = Room.databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + AppDatabase::class.java, + TEST_DB + ).addMigrations(*ALL_MIGRATIONS).build().apply { + insertNewBeacon(openHelper.writableDatabase) + openHelper.writableDatabase.close() + } + + assertDBMigrations(roomDB) + } + + @Test + @Throws(IOException::class) + fun roomMigrate6_7() { + helper.createDatabase(TEST_DB, 6).apply { + insertDummyData(this) + close() + } + + val roomDB = Room.databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + AppDatabase::class.java, + TEST_DB + ).addMigrations(*ALL_MIGRATIONS).build().apply { + insertNewBeacon(openHelper.writableDatabase) + openHelper.writableDatabase.close() + } + + assertDBMigrations(roomDB) + } + + @Test + @Throws(IOException::class) + fun migrate4_5() { + helper.createDatabase(TEST_DB, 4).apply { + insertDummyData(this) + close() + } + + val db = helper.runMigrationsAndValidate(TEST_DB, 5, true, AUTO_MIGRATION_4_5) + insertNewBeacon(db) + } + + @Test + @Throws(IOException::class) + fun migrate5_6() { + helper.createDatabase(TEST_DB, 5).apply { + insertDummyData(this) + close() + } + + val db = helper.runMigrationsAndValidate(TEST_DB, 6, true, AUTO_MIGRATION_5_6) + insertNewBeacon(db) + } + + @Test + @Throws(IOException::class) + fun migrate5_7() { + helper.createDatabase(TEST_DB, 5).apply { + insertDummyData(this) + close() + } + + val db = helper.runMigrationsAndValidate(TEST_DB, 7, true, DatabaseModule.MIGRATION_5_7) + insertNewBeacon(db) + } + +} \ No newline at end of file diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml new file mode 100644 index 00000000..563ac446 --- /dev/null +++ b/app/src/debug/res/values/strings.xml @@ -0,0 +1,4 @@ + + AirGuard-Dev + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fbbca426..029f7b5f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -37,7 +37,8 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.ATTrackingDetection"> + android:theme="@style/Theme.ATTrackingDetection" + android:largeHeap="true"> + android:exported="true"> diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/AppDatabase.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/AppDatabase.kt index 6e1272ef..d0d0aaee 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/database/AppDatabase.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/AppDatabase.kt @@ -14,13 +14,14 @@ import de.seemoo.at_tracking_detection.database.models.Notification import de.seemoo.at_tracking_detection.database.models.device.BaseDevice import de.seemoo.at_tracking_detection.util.converter.DateTimeConverter + @Database( - version = 5, + version = 7, entities = [BaseDevice::class, Notification::class, Beacon::class, Feedback::class], - autoMigrations = [AutoMigration(from = 2, to = 3), AutoMigration(from = 3, to = 4), AutoMigration(from = 4, to = 5)], + autoMigrations = [AutoMigration(from = 2, to = 3), AutoMigration(from = 3, to = 4), AutoMigration(from = 4, to = 5) , AutoMigration(from=5, to=6)], exportSchema = true ) -@TypeConverters(DateTimeConverter::class) +@TypeConverters(Converters::class, DateTimeConverter::class) abstract class AppDatabase : RoomDatabase() { abstract fun deviceDao(): DeviceDao diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/Converters.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/Converters.kt new file mode 100644 index 00000000..f43b156d --- /dev/null +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/Converters.kt @@ -0,0 +1,17 @@ +package de.seemoo.at_tracking_detection.database + +import androidx.room.TypeConverter +import java.time.LocalDateTime + +class Converters { + + @TypeConverter + fun fromStringToList(value: String?): List? { + return value?.split(";") + } + + @TypeConverter + fun toStringFromList(list: List?): String? { + return list?.joinToString(";") + } +} \ No newline at end of file diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Beacon.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Beacon.kt index 647459bb..0dd9a4c1 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Beacon.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Beacon.kt @@ -4,13 +4,14 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import androidx.room.TypeConverters +import de.seemoo.at_tracking_detection.database.Converters import de.seemoo.at_tracking_detection.util.converter.DateTimeConverter import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.time.format.FormatStyle @Entity(tableName = "beacon") -@TypeConverters(DateTimeConverter::class) +@TypeConverters(DateTimeConverter::class, Converters::class) data class Beacon( @PrimaryKey(autoGenerate = true) val beaconId: Int, @ColumnInfo(name = "receivedAt") val receivedAt: LocalDateTime, @@ -18,7 +19,8 @@ data class Beacon( @ColumnInfo(name = "deviceAddress") var deviceAddress: String, @ColumnInfo(name = "longitude") var longitude: Double?, @ColumnInfo(name = "latitude") var latitude: Double?, - @ColumnInfo(name = "mfg") var manufacturerData: ByteArray? + @ColumnInfo(name = "mfg") var manufacturerData: ByteArray?, + @ColumnInfo(name = "serviceUUIDs") var serviceUUIDs: List? ) { constructor( receivedAt: LocalDateTime, @@ -26,7 +28,8 @@ data class Beacon( deviceAddress: String, longitude: Double?, latitude: Double?, - mfg: ByteArray? + mfg: ByteArray?, + serviceUUIDs: List? ) : this( 0, receivedAt, @@ -34,7 +37,8 @@ data class Beacon( deviceAddress, longitude, latitude, - mfg + mfg, + serviceUUIDs ) fun getFormattedDate(): String = diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/BaseDevice.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/BaseDevice.kt index 9f41e5a8..1101cb7e 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/BaseDevice.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/BaseDevice.kt @@ -25,7 +25,7 @@ data class BaseDevice( @ColumnInfo(name = "lastSeen") var lastSeen: LocalDateTime, @ColumnInfo(name = "notificationSent") var notificationSent: Boolean, @ColumnInfo(name = "lastNotificationSent") var lastNotificationSent: LocalDateTime?, - @ColumnInfo(name = "deviceType", defaultValue = "null") val deviceType: DeviceType? + @ColumnInfo(name = "deviceType") val deviceType: DeviceType? ) { constructor( @@ -82,6 +82,7 @@ data class BaseDevice( DeviceType.APPLE -> AppleDevice(deviceId) DeviceType.AIRPODS -> AirPods(deviceId) DeviceType.FIND_MY -> FindMy(deviceId) + DeviceType.TILE -> Tile(deviceId) else -> { // For backwards compatibility if (payloadData?.and(0x10)?.toInt() != 0 && connectable == true) { diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/Device.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/Device.kt index 104f8239..5b3afe4c 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/Device.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/Device.kt @@ -16,4 +16,8 @@ abstract class Device { val context = ATTrackingDetectionApplication.getAppContext() return AppCompatResources.getDrawable(context, imageResource) } + + fun isConnectable(): Boolean { + return this is Connectable + } } \ No newline at end of file diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceManager.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceManager.kt index 21d45a67..6e5b6539 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceManager.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceManager.kt @@ -3,7 +3,6 @@ package de.seemoo.at_tracking_detection.database.models.device import android.bluetooth.le.ScanFilter import android.bluetooth.le.ScanResult import android.content.IntentFilter -import android.util.Log import de.seemoo.at_tracking_detection.database.models.device.types.* import de.seemoo.at_tracking_detection.util.ble.BluetoothConstants import timber.log.Timber @@ -11,39 +10,38 @@ import kotlin.experimental.and object DeviceManager { - val devices = listOf(AirTag, FindMy, AirPods, AppleDevice) + val devices = listOf(AirTag, FindMy, AirPods, AppleDevice, Tile) + val appleDevices = listOf(AirTag, FindMy, AirPods, AppleDevice) fun getDeviceType(scanResult: ScanResult): DeviceType { Timber.d("Checking device type for ${scanResult.device.address}") - val manufacturerData = scanResult.scanRecord?.getManufacturerSpecificData(0x004c) ?: return Unknown.deviceType - val statusByte: Byte = manufacturerData[2] - Timber.d("Status byte $statusByte, ${statusByte.toString(2)}") - // Get the correct int from the byte - val deviceTypeInt = (statusByte.and(0x30).toInt() shr 4) - Timber.d("Device type int: $deviceTypeInt") - var deviceTypeFilter: DeviceType? = null - var deviceTypeCheck: DeviceType? = null - - for (device in devices) { - // Implementation of device detection is incorrect. - - if (device.bluetoothFilter.matches(scanResult)) { - deviceTypeFilter = device.deviceType + val manufacturerData = scanResult.scanRecord?.getManufacturerSpecificData(0x004c) + val services = scanResult.scanRecord?.serviceUuids + if (manufacturerData != null) { + val statusByte: Byte = manufacturerData[2] +// Timber.d("Status byte $statusByte, ${statusByte.toString(2)}") + // Get the correct int from the byte + val deviceTypeInt = (statusByte.and(0x30).toInt() shr 4) +// Timber.d("Device type int: $deviceTypeInt") + + var deviceTypeCheck: DeviceType? = null + + for (device in appleDevices) { + // Implementation of device detection is incorrect. + if (device.statusByteDeviceType == deviceTypeInt.toUInt()) { + deviceTypeCheck = device.deviceType + } } - if (device.statusByteDeviceType == deviceTypeInt.toUInt()) { - deviceTypeCheck = device.deviceType - } - } - if (deviceTypeCheck != deviceTypeFilter) { - Timber.d("Different device types detected: Check $deviceTypeCheck filter: $deviceTypeFilter") + return deviceTypeCheck ?: Unknown.deviceType + }else if (services != null) { + //Check if this device is a Tile + if (services.contains(Tile.offlineFindingServiceUUID)) { + return Tile.deviceType + } } - - val result = deviceTypeCheck ?: deviceTypeFilter ?: Unknown.deviceType - - Timber.d("Result device check: $result") - return result + return Unknown.deviceType } val scanFilter: List = devices.map { it.bluetoothFilter } diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/Tile.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/Tile.kt index cd1365a6..69c5b715 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/Tile.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/Tile.kt @@ -4,6 +4,7 @@ import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGattCallback import android.bluetooth.BluetoothGattCharacteristic import android.bluetooth.le.ScanFilter +import android.os.ParcelUuid import androidx.annotation.DrawableRes import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication import de.seemoo.at_tracking_detection.R @@ -14,7 +15,7 @@ import de.seemoo.at_tracking_detection.database.models.device.DeviceType import de.seemoo.at_tracking_detection.util.ble.BluetoothConstants import timber.log.Timber -class Tile(val id: Int) : Device(), Connectable { +class Tile(val id: Int) : Device(){ override val imageResource: Int @DrawableRes get() = R.drawable.ic_baseline_device_unknown_24 @@ -26,41 +27,10 @@ class Tile(val id: Int) : Device(), Connectable { override val deviceContext: DeviceContext get() = Tile - override val bluetoothGattCallback: BluetoothGattCallback - get() = object : BluetoothGattCallback() { - override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { - when (status) { - BluetoothGatt.GATT_SUCCESS -> { - when (newState) { - else -> { - Timber.d("Connection state changed to $newState") - } - } - } - else -> { - Timber.e("Failed to connect to bluetooth device! Status: $status") - broadcastUpdate(BluetoothConstants.ACTION_EVENT_FAILED) - } - } - } - - override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { - super.onServicesDiscovered(gatt, status) - } - - override fun onCharacteristicRead( - gatt: BluetoothGatt?, - characteristic: BluetoothGattCharacteristic?, - status: Int - ) { - - } - } - companion object : DeviceContext { // TODO: Implement scan filter for tile override val bluetoothFilter: ScanFilter - get() = ScanFilter.Builder().setDeviceAddress("FF:FF:FF:FF:FF:FF").build() + get() = ScanFilter.Builder().setServiceUuid(offlineFindingServiceUUID).build() override val deviceType: DeviceType get() = DeviceType.TILE @@ -70,5 +40,7 @@ class Tile(val id: Int) : Device(), Connectable { override val statusByteDeviceType: UInt get() = 0u + + val offlineFindingServiceUUID: ParcelUuid = ParcelUuid.fromString("0000FEED-0000-1000-8000-00805F9B34FB") } } \ No newline at end of file diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/detection/ScanBluetoothWorker.kt b/app/src/main/java/de/seemoo/at_tracking_detection/detection/ScanBluetoothWorker.kt index 18407e35..0619fadc 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/detection/ScanBluetoothWorker.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/detection/ScanBluetoothWorker.kt @@ -22,6 +22,7 @@ import de.seemoo.at_tracking_detection.database.repository.BeaconRepository import de.seemoo.at_tracking_detection.database.repository.DeviceRepository import de.seemoo.at_tracking_detection.notifications.NotificationService import de.seemoo.at_tracking_detection.util.Util +import de.seemoo.at_tracking_detection.util.ble.BLEScanCallback import de.seemoo.at_tracking_detection.worker.BackgroundWorkScheduler import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay @@ -87,14 +88,12 @@ class ScanBluetoothWorker @AssistedInject constructor( val scanSettings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build() - bluetoothAdapter.bluetoothLeScanner.startScan( - DeviceManager.scanFilter, - scanSettings, - leScanCallback - ) + BLEScanCallback.startScanning(bluetoothAdapter.bluetoothLeScanner, DeviceManager.scanFilter, scanSettings, leScanCallback) delay(getScanDuration()) - bluetoothAdapter.bluetoothLeScanner.stopScan(leScanCallback) + + BLEScanCallback.stopScanning(bluetoothAdapter.bluetoothLeScanner) + Timber.d("Scanning for bluetooth le devices stopped!. Discovered ${scanResultDictionary.size} devices") if (location == null) { @@ -161,16 +160,17 @@ class ScanBluetoothWorker @AssistedInject constructor( Timber.d("Device: $device") + val uuids = scanResult.scanRecord?.serviceUuids?.map { it.toString() }?.toList() val beacon = if (BuildConfig.DEBUG) { // Save the manufacturer data to the beacon Beacon( discoveryDate, scanResult.rssi, scanResult.device.address, latitude, longitude, - scanResult.scanRecord?.bytes + scanResult.scanRecord?.bytes, uuids ) } else { Beacon( discoveryDate, scanResult.rssi, scanResult.device.address, latitude, longitude, - null + null, uuids ) } diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/detection/TrackingDetectorWorker.kt b/app/src/main/java/de/seemoo/at_tracking_detection/detection/TrackingDetectorWorker.kt index 34a396a6..d6b78d7c 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/detection/TrackingDetectorWorker.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/detection/TrackingDetectorWorker.kt @@ -43,6 +43,7 @@ class TrackingDetectorWorker @AssistedInject constructor( //TODO: Can we do this in parallel? cleanedBeaconsPerDevice.forEach { mapEntry -> + //TODO: Implement tracking detection for Tile val device = deviceRepository.getDevice(mapEntry.key) //Check that we found enough beacons @@ -95,6 +96,7 @@ class TrackingDetectorWorker @AssistedInject constructor( LocalDateTime.now(ZoneOffset.UTC).toString() ) ) + //Gets all beacons found in the last scan. Then we get all beacons for the device that emitted one of those beaconRepository.getLatestBeacons(since).forEach { val beacons = beaconRepository.getDeviceBeacons(it.deviceAddress) beaconsPerDevice[it.deviceAddress] = beacons diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/hilt/DatabaseModule.kt b/app/src/main/java/de/seemoo/at_tracking_detection/hilt/DatabaseModule.kt index d9e3dc76..f891fc87 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/hilt/DatabaseModule.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/hilt/DatabaseModule.kt @@ -1,7 +1,10 @@ package de.seemoo.at_tracking_detection.hilt import android.content.Context +import android.database.sqlite.SQLiteException import androidx.room.Room +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -16,16 +19,35 @@ import de.seemoo.at_tracking_detection.database.repository.BeaconRepository import de.seemoo.at_tracking_detection.database.repository.DeviceRepository import de.seemoo.at_tracking_detection.database.repository.FeedbackRepository import de.seemoo.at_tracking_detection.database.repository.NotificationRepository +import timber.log.Timber import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object DatabaseModule { + val MIGRATION_5_7 = object : Migration(5, 7) { + override fun migrate(database: SupportSQLiteDatabase) { + try { + database.execSQL("ALTER TABLE `beacon` ADD COLUMN `serviceUUIDs` TEXT DEFAULT NULL") + }catch (e: SQLiteException) { + Timber.e("Could not create new column ${e}") + } + + } + } + + val MIGRATION_6_7 = object : Migration(6, 7) { + override fun migrate(database: SupportSQLiteDatabase) { + } + } + + @Provides @Singleton fun provideDatabase(@ApplicationContext context: Context): AppDatabase { return Room.databaseBuilder(context, AppDatabase::class.java, "attd_db") + .addMigrations(MIGRATION_5_7, MIGRATION_6_7) .allowMainThreadQueries().build() } diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugFragment.kt index 1bf54270..3a7254bd 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugFragment.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugFragment.kt @@ -29,6 +29,7 @@ import de.seemoo.at_tracking_detection.notifications.NotificationService import de.seemoo.at_tracking_detection.statistics.api.Api import de.seemoo.at_tracking_detection.ui.dashboard.DashboardRiskFragmentDirections import de.seemoo.at_tracking_detection.util.Util +import de.seemoo.at_tracking_detection.util.ble.BLEScanCallback import de.seemoo.at_tracking_detection.worker.BackgroundWorkScheduler import fr.bipi.tressence.file.FileLoggerTree import kotlinx.coroutines.CoroutineScope @@ -112,6 +113,15 @@ class DebugFragment : Fragment() { DebugFragmentDirections.actionNavigationDebugToDebugLogFragment() findNavController().navigate(directions) } + + view.findViewById