diff --git a/api.properties b/api.properties index 157eb729..f4ba31f9 100644 --- a/api.properties +++ b/api.properties @@ -1,2 +1,2 @@ -API_KEY="AGdk9qPF.Bl9yatnjjoy9WEEjXYGVewE7ZyJET9Yy" +API_KEY="aGUuBakn.mm4dTiqKAfN6akqkzggODoJjmMkGAoWL" 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 103898b2..7352de2d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,9 +3,11 @@ plugins { id 'kotlin-android' id 'androidx.navigation.safeargs.kotlin' id 'kotlin-kapt' + id 'com.google.devtools.ksp' id 'dagger.hilt.android.plugin' id 'com.mikepenz.aboutlibraries.plugin' - id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.compose' + id 'org.jetbrains.kotlin.plugin.compose' version "$kotlin_version" } def apiPropertiesFile = rootProject.file("api.properties") @@ -26,19 +28,13 @@ android { applicationId "de.seemoo.at_tracking_detection" minSdkVersion 28 targetSdk = 34 - versionCode 44 - versionName "2.2" + versionCode 45 + versionName "2.3" buildConfigField "String", "API_KEY", apiProperties["API_KEY"] buildConfigField "String", "API_BASE_ADDRESS", apiProperties["API_BASE_ADDRESS"] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - - kapt { - arguments { - arg("room.schemaLocation", "$projectDir/schemas") - } - } } buildTypes { @@ -65,11 +61,6 @@ android { jvmTarget = JavaVersion.VERSION_17.toString() } - composeOptions { - kotlinCompilerExtensionVersion "1.5.11" - } - - sourceSets { // Adds exported schema location as test app assets. androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) @@ -95,14 +86,14 @@ dependencies { implementation 'com.github.bastienpaulfr:Treessence:1.0.0' implementation "androidx.work:work-runtime-ktx:$work_version" implementation 'androidx.core:core-ktx:1.13.1' - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.vectordrawable:vectordrawable:1.2.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7' implementation 'androidx.navigation:navigation-ui-ktx:2.7.7' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.3' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.preference:preference-ktx:1.2.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -110,16 +101,16 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' - implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12' + implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.14' implementation 'com.google.code.gson:gson:2.10.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation "androidx.work:work-testing:$work_version" implementation 'androidx.core:core-ktx:1.13.1' - debugImplementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12' + debugImplementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14' - implementation "com.google.dagger:hilt-android:$hilt_version" - implementation 'androidx.hilt:hilt-work:1.2.0' - implementation 'androidx.hilt:hilt-navigation-fragment:1.2.0' + implementation "com.google.dagger:hilt-android:$hilt_compiler_version" + implementation "androidx.hilt:hilt-work:$hilt_version" + implementation "androidx.hilt:hilt-navigation-fragment:$hilt_version" implementation 'com.github.AppIntro:AppIntro:6.1.0' @@ -134,23 +125,22 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.16.0' - - kapt "com.google.dagger:hilt-compiler:$hilt_version" - kapt 'androidx.hilt:hilt-compiler:1.2.0' + ksp "com.google.dagger:hilt-compiler:$hilt_compiler_version" + ksp "androidx.hilt:hilt-compiler:$hilt_version" implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-ktx:$room_version" - kapt "androidx.room:room-compiler:$room_version" + ksp "androidx.room:room-compiler:$room_version" - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' androidTestImplementation "androidx.room:room-testing:$room_version" - androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0' - androidTestImplementation 'androidx.test:core:1.5.0' - androidTestImplementation 'androidx.test:core-ktx:1.5.0' - androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5' - androidTestImplementation 'androidx.test:runner:1.5.2' - androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1' + androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1' + androidTestImplementation 'androidx.test:core:1.6.1' + androidTestImplementation 'androidx.test:core-ktx:1.6.1' + androidTestImplementation 'androidx.test.ext:junit-ktx:1.2.1' + androidTestImplementation 'androidx.test:runner:1.6.1' + androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.6.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' @@ -161,16 +151,19 @@ dependencies { // Integration with activities implementation 'androidx.activity:activity-compose:1.9.0' // Compose Material Design - implementation 'androidx.compose.material:material:1.6.7' + implementation 'androidx.compose.material:material:1.6.8' // Animations - implementation 'androidx.compose.animation:animation:1.6.7' + implementation 'androidx.compose.animation:animation:1.6.8' // Tooling support (Previews, etc.) - implementation 'androidx.compose.ui:ui-tooling:1.6.7' + implementation 'androidx.compose.ui:ui-tooling:1.6.8' // Integration with ViewModels - implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.3' // UI Tests - androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.7' + androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.8' // When using a MDC theme implementation "com.google.android.material:compose-theme-adapter:1.2.1" +} +ksp { + arg("room.schemaLocation", "$projectDir/schemas") } \ No newline at end of file diff --git a/app/src/androidTest/java/de/seemoo/at_tracking_detection/DevicesTabUITest.kt b/app/src/androidTest/java/de/seemoo/at_tracking_detection/DevicesTabUITest.kt index 515f3c29..32457dff 100644 --- a/app/src/androidTest/java/de/seemoo/at_tracking_detection/DevicesTabUITest.kt +++ b/app/src/androidTest/java/de/seemoo/at_tracking_detection/DevicesTabUITest.kt @@ -90,7 +90,7 @@ class DevicesTabUITest { //Disabling all devices onView(withText("AirTag")).perform(click()) - onView(withText("FindMy Device")).perform(click()) + onView(withText("AppleFindMy Device")).perform(click()) onView(withText("AirPods")).perform(click()) onView(withText("Apple Device")).perform(click()) onView(withText("Galaxy SmartTag")).perform(click()) @@ -101,7 +101,7 @@ class DevicesTabUITest { //Enabling all devices onView(withText("AirTag")).perform(click()) - onView(withText("FindMy Device")).perform(click()) + onView(withText("AppleFindMy Device")).perform(click()) onView(withText("AirPods")).perform(click()) onView(withText("Apple Device")).perform(click()) onView(withText("Galaxy SmartTag")).perform(click()) 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 464088a1..5a4f418d 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 @@ -103,12 +103,14 @@ data class BaseDevice( DeviceType.UNKNOWN -> Unknown(deviceId) DeviceType.APPLE -> AppleDevice(deviceId) DeviceType.AIRPODS -> AirPods(deviceId) - DeviceType.FIND_MY -> FindMy(deviceId) + DeviceType.FIND_MY -> AppleFindMy(deviceId) DeviceType.TILE -> Tile(deviceId) DeviceType.CHIPOLO -> Chipolo(deviceId) + DeviceType.PEBBLEBEE -> PebbleBee(deviceId) DeviceType.SAMSUNG -> SamsungDevice(deviceId) DeviceType.GALAXY_SMART_TAG -> SmartTag(deviceId) DeviceType.GALAXY_SMART_TAG_PLUS -> SmartTagPlus(deviceId) + DeviceType.GOOGLE_FIND_MY_NETWORK -> GoogleFindMyNetwork(deviceId) else -> { // For backwards compatibility if (payloadData?.and(0x10)?.toInt() != 0 && connectable == true) { @@ -145,6 +147,7 @@ data class BaseDevice( return when (deviceType) { DeviceType.TILE -> Tile.getConnectionState(scanResult) DeviceType.CHIPOLO -> Chipolo.getConnectionState(scanResult) + DeviceType.PEBBLEBEE -> PebbleBee.getConnectionState(scanResult) DeviceType.SAMSUNG, DeviceType.GALAXY_SMART_TAG, DeviceType.GALAXY_SMART_TAG_PLUS -> SamsungDevice.getConnectionState(scanResult) @@ -152,6 +155,7 @@ data class BaseDevice( DeviceType.FIND_MY, DeviceType.AIRTAG, DeviceType.APPLE -> AppleDevice.getConnectionState(scanResult) + DeviceType.GOOGLE_FIND_MY_NETWORK -> GoogleFindMyNetwork.getConnectionState(scanResult) else -> ConnectionState.UNKNOWN } } 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 3573278b..87575934 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 @@ -10,8 +10,8 @@ import kotlin.experimental.and object DeviceManager { - val devices = listOf(AirTag, FindMy, AirPods, AppleDevice, SmartTag, SmartTagPlus, Tile, Chipolo) - private val appleDevices = listOf(AirTag, FindMy, AirPods, AppleDevice) + val devices = listOf(AirTag, AppleFindMy, AirPods, AppleDevice, SmartTag, SmartTagPlus, Tile, Chipolo, PebbleBee, GoogleFindMyNetwork) + private val appleDevices = listOf(AirTag, AppleFindMy, AirPods, AppleDevice) val unsafeConnectionState = listOf(ConnectionState.OVERMATURE_OFFLINE, ConnectionState.UNKNOWN) val savedConnectionStates = unsafeConnectionState //enumValues().toList() @@ -32,7 +32,7 @@ object DeviceManager { } } - fun getDeviceTypeFromCache(deviceAddress: String): DeviceType? { + private fun getDeviceTypeFromCache(deviceAddress: String): DeviceType? { deviceTypeCache[deviceAddress]?.let { cachedDeviceType -> return cachedDeviceType } @@ -56,10 +56,15 @@ object DeviceManager { } } + if (scanRecord.serviceData.contains(GoogleFindMyNetwork.offlineFindingServiceUUID)) { + return GoogleFindMyNetwork.deviceType + } + scanRecord.serviceUuids?.let { services -> when { services.contains(Tile.offlineFindingServiceUUID) -> return Tile.deviceType services.contains(Chipolo.offlineFindingServiceUUID) -> return Chipolo.deviceType + services.contains(PebbleBee.offlineFindingServiceUUID) -> return PebbleBee.deviceType services.contains(SmartTag.offlineFindingServiceUUID) -> return SamsungDevice.getSamsungDeviceType(scanResult) else -> return Unknown.deviceType } @@ -75,11 +80,13 @@ object DeviceManager { DeviceType.APPLE -> AppleDevice.websiteManufacturer DeviceType.AIRPODS -> AirPods.websiteManufacturer DeviceType.TILE -> Tile.websiteManufacturer - DeviceType.FIND_MY -> FindMy.websiteManufacturer + DeviceType.FIND_MY -> AppleFindMy.websiteManufacturer DeviceType.CHIPOLO -> Chipolo.websiteManufacturer + DeviceType.PEBBLEBEE -> PebbleBee.websiteManufacturer DeviceType.SAMSUNG -> SamsungDevice.websiteManufacturer DeviceType.GALAXY_SMART_TAG -> SmartTag.websiteManufacturer DeviceType.GALAXY_SMART_TAG_PLUS -> SmartTagPlus.websiteManufacturer + DeviceType.GOOGLE_FIND_MY_NETWORK -> GoogleFindMyNetwork.websiteManufacturer } } @@ -91,7 +98,9 @@ object DeviceManager { return DeviceType.valueOf(deviceTypeString) } - val scanFilter: List = devices.map { it.bluetoothFilter } + val scanFilter: List = devices.map { + it.bluetoothFilter + } val gattIntentFilter: IntentFilter = IntentFilter().apply { addAction(BluetoothConstants.ACTION_EVENT_RUNNING) diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceType.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceType.kt index 639882d4..e772b16d 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceType.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceType.kt @@ -12,9 +12,11 @@ enum class DeviceType { TILE, FIND_MY, CHIPOLO, + PEBBLEBEE, SAMSUNG, GALAXY_SMART_TAG, - GALAXY_SMART_TAG_PLUS; + GALAXY_SMART_TAG_PLUS, + GOOGLE_FIND_MY_NETWORK; companion object { fun userReadableName(deviceType: DeviceType): String { @@ -23,12 +25,14 @@ enum class DeviceType { AIRPODS -> AirPods.defaultDeviceName AIRTAG -> AirTag.defaultDeviceName APPLE -> AppleDevice.defaultDeviceName - FIND_MY -> FindMy.defaultDeviceName + FIND_MY -> AppleFindMy.defaultDeviceName TILE -> Tile.defaultDeviceName + PEBBLEBEE -> PebbleBee.defaultDeviceName CHIPOLO -> Chipolo.defaultDeviceName SAMSUNG -> SamsungDevice.defaultDeviceName GALAXY_SMART_TAG -> SmartTag.defaultDeviceName GALAXY_SMART_TAG_PLUS -> SmartTagPlus.defaultDeviceName + GOOGLE_FIND_MY_NETWORK -> GoogleFindMyNetwork.defaultDeviceName } } @@ -41,9 +45,11 @@ enum class DeviceType { FIND_MY -> R.drawable.ic_chipolo TILE -> R.drawable.ic_tile CHIPOLO -> R.drawable.ic_chipolo + PEBBLEBEE -> R.drawable.ic_baseline_device_unknown_24 SAMSUNG -> R.drawable.ic_baseline_device_unknown_24 GALAXY_SMART_TAG -> R.drawable.ic_smarttag_icon GALAXY_SMART_TAG_PLUS -> R.drawable.ic_smarttag_icon + GOOGLE_FIND_MY_NETWORK -> R.drawable.ic_chipolo } } @@ -64,6 +70,8 @@ enum class DeviceType { allowedDeviceTypes.add(GALAXY_SMART_TAG_PLUS) } "tiles" -> allowedDeviceTypes.add(TILE) + "pebblebees" -> allowedDeviceTypes.add(PEBBLEBEE) + "google_find_my_network" -> allowedDeviceTypes.add(GOOGLE_FIND_MY_NETWORK) } } @@ -77,6 +85,7 @@ enum class DeviceType { return when (this) { TILE -> true CHIPOLO -> true + PEBBLEBEE -> true else -> false } } @@ -85,14 +94,16 @@ enum class DeviceType { return when (this) { TILE -> Tile.numberOfHoursToBeConsideredForTrackingDetection CHIPOLO -> Chipolo.numberOfHoursToBeConsideredForTrackingDetection + PEBBLEBEE -> PebbleBee.numberOfHoursToBeConsideredForTrackingDetection UNKNOWN -> Unknown.numberOfHoursToBeConsideredForTrackingDetection AIRPODS -> AirPods.numberOfHoursToBeConsideredForTrackingDetection AIRTAG -> AirTag.numberOfHoursToBeConsideredForTrackingDetection APPLE -> AppleDevice.numberOfHoursToBeConsideredForTrackingDetection - FIND_MY -> FindMy.numberOfHoursToBeConsideredForTrackingDetection + FIND_MY -> AppleFindMy.numberOfHoursToBeConsideredForTrackingDetection SAMSUNG -> SamsungDevice.numberOfHoursToBeConsideredForTrackingDetection GALAXY_SMART_TAG -> SmartTag.numberOfHoursToBeConsideredForTrackingDetection GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfHoursToBeConsideredForTrackingDetection + GOOGLE_FIND_MY_NETWORK -> GoogleFindMyNetwork.numberOfHoursToBeConsideredForTrackingDetection } } @@ -100,14 +111,16 @@ enum class DeviceType { return when (this) { TILE -> Tile.numberOfLocationsToBeConsideredForTrackingDetectionLow CHIPOLO -> Chipolo.numberOfLocationsToBeConsideredForTrackingDetectionLow + PEBBLEBEE -> PebbleBee.numberOfLocationsToBeConsideredForTrackingDetectionLow UNKNOWN -> Unknown.numberOfLocationsToBeConsideredForTrackingDetectionLow AIRPODS -> AirPods.numberOfLocationsToBeConsideredForTrackingDetectionLow AIRTAG -> AirTag.numberOfLocationsToBeConsideredForTrackingDetectionLow APPLE -> AppleDevice.numberOfLocationsToBeConsideredForTrackingDetectionLow - FIND_MY -> FindMy.numberOfLocationsToBeConsideredForTrackingDetectionLow + FIND_MY -> AppleFindMy.numberOfLocationsToBeConsideredForTrackingDetectionLow SAMSUNG -> SamsungDevice.numberOfLocationsToBeConsideredForTrackingDetectionLow GALAXY_SMART_TAG -> SmartTag.numberOfLocationsToBeConsideredForTrackingDetectionLow GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfLocationsToBeConsideredForTrackingDetectionLow + GOOGLE_FIND_MY_NETWORK -> GoogleFindMyNetwork.numberOfLocationsToBeConsideredForTrackingDetectionLow } } @@ -115,14 +128,16 @@ enum class DeviceType { return when (this) { TILE -> Tile.numberOfLocationsToBeConsideredForTrackingDetectionMedium CHIPOLO -> Chipolo.numberOfLocationsToBeConsideredForTrackingDetectionMedium + PEBBLEBEE -> PebbleBee.numberOfLocationsToBeConsideredForTrackingDetectionMedium UNKNOWN -> Unknown.numberOfLocationsToBeConsideredForTrackingDetectionMedium AIRPODS -> AirPods.numberOfLocationsToBeConsideredForTrackingDetectionMedium AIRTAG -> AirTag.numberOfLocationsToBeConsideredForTrackingDetectionMedium APPLE -> AppleDevice.numberOfLocationsToBeConsideredForTrackingDetectionMedium - FIND_MY -> FindMy.numberOfLocationsToBeConsideredForTrackingDetectionMedium + FIND_MY -> AppleFindMy.numberOfLocationsToBeConsideredForTrackingDetectionMedium SAMSUNG -> SamsungDevice.numberOfLocationsToBeConsideredForTrackingDetectionMedium GALAXY_SMART_TAG -> SmartTag.numberOfLocationsToBeConsideredForTrackingDetectionMedium GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfLocationsToBeConsideredForTrackingDetectionMedium + GOOGLE_FIND_MY_NETWORK -> GoogleFindMyNetwork.numberOfLocationsToBeConsideredForTrackingDetectionMedium } } @@ -130,14 +145,16 @@ enum class DeviceType { return when (this) { TILE -> Tile.numberOfLocationsToBeConsideredForTrackingDetectionHigh CHIPOLO -> Chipolo.numberOfLocationsToBeConsideredForTrackingDetectionHigh + PEBBLEBEE -> PebbleBee.numberOfLocationsToBeConsideredForTrackingDetectionHigh UNKNOWN -> Unknown.numberOfLocationsToBeConsideredForTrackingDetectionHigh AIRPODS -> AirPods.numberOfLocationsToBeConsideredForTrackingDetectionHigh AIRTAG -> AirTag.numberOfLocationsToBeConsideredForTrackingDetectionHigh APPLE -> AppleDevice.numberOfLocationsToBeConsideredForTrackingDetectionHigh - FIND_MY -> FindMy.numberOfLocationsToBeConsideredForTrackingDetectionHigh + FIND_MY -> AppleFindMy.numberOfLocationsToBeConsideredForTrackingDetectionHigh SAMSUNG -> SamsungDevice.numberOfLocationsToBeConsideredForTrackingDetectionHigh GALAXY_SMART_TAG -> SmartTag.numberOfLocationsToBeConsideredForTrackingDetectionHigh GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfLocationsToBeConsideredForTrackingDetectionHigh + GOOGLE_FIND_MY_NETWORK -> GoogleFindMyNetwork.numberOfLocationsToBeConsideredForTrackingDetectionHigh } } } \ No newline at end of file diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AirPods.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AirPods.kt index 7c024380..82aec7b1 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AirPods.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AirPods.kt @@ -166,7 +166,7 @@ class AirPods(val id: Int) : Device(), Connectable { get() = "https://www.apple.com/airpods/" override val defaultDeviceName: String - get() = "AirPods" + get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.airpods_default_name) override val statusByteDeviceType: UInt get() = 3u diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AirTag.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AirTag.kt index ea815acb..a3f81935 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AirTag.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AirTag.kt @@ -138,7 +138,7 @@ class AirTag(val id: Int) : Device(), Connectable { get() = DeviceType.AIRTAG override val defaultDeviceName: String - get() = "AirTag" + get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.airtag_default_name) override val statusByteDeviceType: UInt get() = 1u diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AppleDevice.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AppleDevice.kt index 7251ab81..841908c1 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AppleDevice.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AppleDevice.kt @@ -40,7 +40,7 @@ class AppleDevice(val id: Int) : Device() { get() = DeviceType.APPLE override val defaultDeviceName: String - get() = "Apple Device" + get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.apple_device_default_name) override val minTrackingTime: Int get() = 150 * 60 diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/FindMy.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AppleFindMy.kt similarity index 96% rename from app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/FindMy.kt rename to app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AppleFindMy.kt index e4b0576c..8fad0c8e 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/FindMy.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/AppleFindMy.kt @@ -20,18 +20,18 @@ import de.seemoo.at_tracking_detection.util.ble.BluetoothConstants import timber.log.Timber import java.util.* -class FindMy(val id: Int) : Device(), Connectable { +class AppleFindMy(val id: Int) : Device(), Connectable { override val imageResource: Int @DrawableRes get() = R.drawable.ic_chipolo override val defaultDeviceNameWithId: String - get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.device_name_find_my_device) + get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.device_name_find_my_device_apple) .format(id) override val deviceContext: DeviceContext - get() = FindMy + get() = AppleFindMy override val bluetoothGattCallback: BluetoothGattCallback @@ -183,7 +183,7 @@ class FindMy(val id: Int) : Device(), Connectable { get() = "https://www.apple.com/" override val defaultDeviceName: String - get() = "FindMy Device" + get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.apple_find_my_default_name) override val statusByteDeviceType: UInt get() = 2u diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/Chipolo.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/Chipolo.kt index 0e8dbe3c..8b5afa6c 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/Chipolo.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/Chipolo.kt @@ -1,14 +1,13 @@ package de.seemoo.at_tracking_detection.database.models.device.types import android.bluetooth.le.ScanFilter -import android.bluetooth.le.ScanResult import android.os.ParcelUuid import androidx.annotation.DrawableRes import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication import de.seemoo.at_tracking_detection.R -import de.seemoo.at_tracking_detection.database.models.device.* -import de.seemoo.at_tracking_detection.util.Utility.getBitsFromByte -import timber.log.Timber +import de.seemoo.at_tracking_detection.database.models.device.Device +import de.seemoo.at_tracking_detection.database.models.device.DeviceContext +import de.seemoo.at_tracking_detection.database.models.device.DeviceType class Chipolo(val id: Int) : Device() { override val imageResource: Int @@ -32,7 +31,7 @@ class Chipolo(val id: Int) : Device() { get() = DeviceType.CHIPOLO override val defaultDeviceName: String - get() = "Chipolo" + get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.chipolo_default_name) override val statusByteDeviceType: UInt get() = 0u @@ -40,29 +39,6 @@ class Chipolo(val id: Int) : Device() { override val websiteManufacturer: String get() = "https://chipolo.net/" - - val offlineFindingServiceUUID: ParcelUuid = ParcelUuid.fromString("0000FE33-0000-1000-8000-00805F9B34FB") - - override fun getConnectionState(scanResult: ScanResult): ConnectionState { - val serviceData = scanResult.scanRecord?.getServiceData(offlineFindingServiceUUID) - - if (serviceData != null) { - // The last bit of the second byte indicates the offline mode - // 0 --> Device was connected in the last 30 minutes - // 1 --> Last Connection with owner device was longer than 30 minutes ago - val statusBit = getBitsFromByte(serviceData[1], 0) - - return if (statusBit) { - Timber.d("Chipolo: Overmature Offline Mode") - ConnectionState.OVERMATURE_OFFLINE - } else { - Timber.d("Chipolo: Premature Offline Mode") - ConnectionState.PREMATURE_OFFLINE - } - } - - return ConnectionState.UNKNOWN - } } } \ No newline at end of file diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/GoogleFindMyNetwork.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/GoogleFindMyNetwork.kt new file mode 100644 index 00000000..dbaa0319 --- /dev/null +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/GoogleFindMyNetwork.kt @@ -0,0 +1,213 @@ +package de.seemoo.at_tracking_detection.database.models.device.types + +import android.annotation.SuppressLint +import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothGattCallback +import android.bluetooth.BluetoothGattCharacteristic +import android.bluetooth.BluetoothProfile +import android.bluetooth.le.ScanFilter +import android.os.ParcelUuid +import android.bluetooth.le.ScanResult +import android.os.Build +import android.os.Handler +import android.os.Looper +import androidx.annotation.DrawableRes +import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication +import de.seemoo.at_tracking_detection.R +import de.seemoo.at_tracking_detection.database.models.device.Connectable +import de.seemoo.at_tracking_detection.database.models.device.ConnectionState +import de.seemoo.at_tracking_detection.database.models.device.Device +import de.seemoo.at_tracking_detection.database.models.device.DeviceContext +import de.seemoo.at_tracking_detection.database.models.device.DeviceType +import de.seemoo.at_tracking_detection.util.Utility +import de.seemoo.at_tracking_detection.util.ble.BluetoothConstants +import timber.log.Timber +import java.util.UUID + +class GoogleFindMyNetwork(val id: Int) : Device(), Connectable { + + override val imageResource: Int + @DrawableRes + get() = R.drawable.ic_chipolo + + override val defaultDeviceNameWithId: String + get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.device_name_find_my_device_google) + .format(id) + + override val deviceContext: DeviceContext + get() = GoogleFindMyNetwork + + override val bluetoothGattCallback: BluetoothGattCallback + get() = object : BluetoothGattCallback() { + @SuppressLint("MissingPermission") + override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { + when (status) { + BluetoothGatt.GATT_SUCCESS -> { + when (newState) { + BluetoothProfile.STATE_CONNECTED -> { + Timber.d("Connected to gatt device!") + gatt.discoverServices() + } + BluetoothProfile.STATE_DISCONNECTED -> { + broadcastUpdate(BluetoothConstants.ACTION_GATT_DISCONNECTED) + Timber.d("Disconnected from gatt device!") + } + else -> { + Timber.d("Connection state changed to $newState") + } + } + } + else -> { + Timber.e("Failed to connect to bluetooth device! Status: $status") + broadcastUpdate(BluetoothConstants.ACTION_EVENT_FAILED) + } + } + } + + @SuppressLint("MissingPermission") + override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { + val uuids = gatt.services.map { it.uuid } + Timber.d("Found UUIDS $uuids") + val service = gatt.services.firstOrNull { + it.uuid.toString().lowercase().contains( + GOOGLE_SOUND_SERVICE.lowercase() + ) + } + + if (service == null) { + Timber.e("Playing sound service not found!") + disconnect(gatt) + broadcastUpdate(BluetoothConstants.ACTION_EVENT_FAILED) + return + } + + val characteristic = service.getCharacteristic(GOOGLE_SOUND_CHARACTERISTIC) + + characteristic.let { + gatt.setCharacteristicNotification(it, true) + if (Build.VERSION.SDK_INT >= 33) { + it.writeType + gatt.writeCharacteristic(it, GOOGLE_START_SOUND_OPCODE, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) + } else { + // Deprecated since 33 + @Suppress("DEPRECATION") + it.value = GOOGLE_START_SOUND_OPCODE + @Suppress("DEPRECATION") + gatt.writeCharacteristic(it) + } + Timber.d("Playing sound on Find My device with ${it.uuid}") + broadcastUpdate(BluetoothConstants.ACTION_EVENT_RUNNING) + } + } + + @SuppressLint("MissingPermission") + fun stopSoundOnGoogleDevice(gatt: BluetoothGatt) { + val service = gatt.services.firstOrNull { + it.uuid.toString().lowercase().contains( + GOOGLE_SOUND_SERVICE + ) + } + + if (service == null) { + Timber.d("Sound service not found") + return + } + + val uuid = GOOGLE_SOUND_CHARACTERISTIC + val characteristic = service.getCharacteristic(uuid) + characteristic.let { + gatt.setCharacteristicNotification(it, true) + if (Build.VERSION.SDK_INT >= 33) { + it.writeType + gatt.writeCharacteristic(it, GOOGLE_STOP_SOUND_OPCODE, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) + } else { + // Deprecated since 33 + @Suppress("DEPRECATION") + it.value = GOOGLE_STOP_SOUND_OPCODE + @Suppress("DEPRECATION") + gatt.writeCharacteristic(it) + } + Timber.d("Stopping sound on Find My device with ${it.uuid}") + } + } + + @SuppressLint("MissingPermission") + override fun onCharacteristicWrite( + gatt: BluetoothGatt?, + characteristic: BluetoothGattCharacteristic?, + status: Int + ) { + if (status == BluetoothGatt.GATT_SUCCESS) { + Timber.d("Finished writing to characteristic") + if (characteristic?.value.contentEquals(GOOGLE_START_SOUND_OPCODE) && gatt != null) { + Handler(Looper.getMainLooper()).postDelayed({ + stopSoundOnGoogleDevice(gatt) + }, 5000) + } + + if (characteristic?.value.contentEquals(GOOGLE_STOP_SOUND_OPCODE)) { + disconnect(gatt) + broadcastUpdate(BluetoothConstants.ACTION_EVENT_COMPLETED) + } + + } else { + Timber.d("Writing to characteristic failed ${characteristic?.uuid}") + disconnect(gatt) + broadcastUpdate(BluetoothConstants.ACTION_EVENT_FAILED) + } + super.onCharacteristicWrite(gatt, characteristic, status) + } + } + + companion object : DeviceContext { + internal const val GOOGLE_SOUND_SERVICE = "12F4" + internal val GOOGLE_SOUND_CHARACTERISTIC = UUID.fromString("8E0C0001-1D68-FB92-BF61-48377421680E") + internal val GOOGLE_START_SOUND_OPCODE = byteArrayOf(0x00, 0x03) + internal val GOOGLE_STOP_SOUND_OPCODE = byteArrayOf(0x01, 0x03) + + override val deviceType: DeviceType + get() = DeviceType.GOOGLE_FIND_MY_NETWORK + + override val defaultDeviceName: String + get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.google_find_my_default_name) + + override val websiteManufacturer: String + get() = "https://www.google.com/android/find/" + + override val statusByteDeviceType: UInt + get() = 0u + + override val bluetoothFilter: ScanFilter + get() = ScanFilter.Builder() + .setServiceData( + offlineFindingServiceUUID, + byteArrayOf((0x40).toByte()), + byteArrayOf((0x00).toByte())) + .build() + + val offlineFindingServiceUUID: ParcelUuid = ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB") + + override fun getConnectionState(scanResult: ScanResult): ConnectionState { + val serviceData = scanResult.scanRecord?.getServiceData(offlineFindingServiceUUID) + + if (serviceData != null) { + // The last bit of the first byte indicates the offline mode + // 0 --> Device was connected in the last 4 hours + // 1 --> Last Connection with owner device was longer than 4 hours ago + + val statusBit = Utility.getBitsFromByte(serviceData[0], 0) + + return if (statusBit) { + Timber.d("Google Find My: Overmature Offline Mode") + ConnectionState.OVERMATURE_OFFLINE + } else { + Timber.d("Google Find My: Premature Offline Mode") + ConnectionState.PREMATURE_OFFLINE + } + } + + return ConnectionState.UNKNOWN + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/PebbleBee.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/PebbleBee.kt new file mode 100644 index 00000000..850f6a63 --- /dev/null +++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/PebbleBee.kt @@ -0,0 +1,194 @@ +package de.seemoo.at_tracking_detection.database.models.device.types + +import android.annotation.SuppressLint +import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothGattCallback +import android.bluetooth.BluetoothGattCharacteristic +import android.bluetooth.BluetoothProfile +import android.bluetooth.le.ScanFilter +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.os.ParcelUuid +import androidx.annotation.DrawableRes +import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication +import de.seemoo.at_tracking_detection.R +import de.seemoo.at_tracking_detection.database.models.device.Connectable +import de.seemoo.at_tracking_detection.database.models.device.Device +import de.seemoo.at_tracking_detection.database.models.device.DeviceContext +import de.seemoo.at_tracking_detection.database.models.device.DeviceType +import de.seemoo.at_tracking_detection.util.ble.BluetoothConstants +import timber.log.Timber +import java.util.UUID + +class PebbleBee (val id: Int) : Device(), Connectable { + override val imageResource: Int + @DrawableRes + get() = R.drawable.ic_baseline_device_unknown_24 + + override val defaultDeviceNameWithId: String + get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.device_name_pebblebee) + .format(id) + + override val deviceContext: DeviceContext + get() = PebbleBee + + override val bluetoothGattCallback: BluetoothGattCallback + get() = object : BluetoothGattCallback() { + @SuppressLint("MissingPermission") + override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { + when (status) { + BluetoothGatt.GATT_SUCCESS -> { + when (newState) { + BluetoothProfile.STATE_CONNECTED -> { + Timber.d("Connected to gatt device!") + gatt.discoverServices() + } + BluetoothProfile.STATE_DISCONNECTED -> { + broadcastUpdate(BluetoothConstants.ACTION_GATT_DISCONNECTED) + Timber.d("Disconnected from gatt device!") + } + else -> { + Timber.d("Connection state changed to $newState") + } + } + } + else -> { + Timber.e("Failed to connect to bluetooth device! Status: $status") + broadcastUpdate(BluetoothConstants.ACTION_EVENT_FAILED) + } + } + } + + @SuppressLint("MissingPermission") + override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { + if (status != BluetoothGatt.GATT_SUCCESS) { + Timber.e("Service discovery failed with status $status") + return + } + + val uuids = gatt.services.map { it.uuid } + Timber.d("Found UUIDS $uuids") + + val service = gatt.services.firstOrNull { + it.uuid.toString().lowercase().contains(PEBBLEBEE_SOUND_SERVICE.lowercase()) + } + + if (service == null) { + Timber.e("Playing sound service not found!") + disconnect(gatt) + broadcastUpdate(BluetoothConstants.ACTION_EVENT_FAILED) + return + } + + val characteristics = service.characteristics.map { it.uuid } + Timber.d("Found characteristics $characteristics") + + val characteristic = service.getCharacteristic(PEBBLEBEE_SOUND_CHARACTERISTIC) + if (characteristic == null) { + Timber.e("Characteristic not found!") + disconnect(gatt) + broadcastUpdate(BluetoothConstants.ACTION_EVENT_FAILED) + return + } + + gatt.setCharacteristicNotification(characteristic, true) + if (Build.VERSION.SDK_INT >= 33) { + characteristic.writeType + gatt.writeCharacteristic(characteristic, PEBBLEBEE_START_SOUND_OPCODE, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) + } else { + @Suppress("DEPRECATION") + characteristic.value = PEBBLEBEE_START_SOUND_OPCODE + @Suppress("DEPRECATION") + gatt.writeCharacteristic(characteristic) + } + + Timber.d("Playing sound on Find My device with ${characteristic.uuid}") + broadcastUpdate(BluetoothConstants.ACTION_EVENT_RUNNING) + } + + @SuppressLint("MissingPermission") + fun stopSoundOnPebbleBeeDevice(gatt: BluetoothGatt) { + val service = gatt.services.firstOrNull { + it.uuid.toString().lowercase().contains( + PEBBLEBEE_SOUND_SERVICE + ) + } + + if (service == null) { + Timber.d("Sound service not found") + return + } + + val uuid = PEBBLEBEE_SOUND_CHARACTERISTIC + val characteristic = service.getCharacteristic(uuid) + characteristic.let { + gatt.setCharacteristicNotification(it, true) + if (Build.VERSION.SDK_INT >= 33) { + it.writeType + gatt.writeCharacteristic(it, PEBBLEBEE_STOP_SOUND_OPCODE, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) + } else { + // Deprecated since 33 + @Suppress("DEPRECATION") + it.value = PEBBLEBEE_STOP_SOUND_OPCODE + @Suppress("DEPRECATION") + gatt.writeCharacteristic(it) + } + Timber.d("Stopping sound on Find My device with ${it.uuid}") + } + } + + @SuppressLint("MissingPermission") + override fun onCharacteristicWrite( + gatt: BluetoothGatt?, + characteristic: BluetoothGattCharacteristic?, + status: Int + ) { + if (status == BluetoothGatt.GATT_SUCCESS) { + Timber.d("Finished writing to characteristic") + if (characteristic?.value.contentEquals(PEBBLEBEE_START_SOUND_OPCODE) && gatt != null) { + Handler(Looper.getMainLooper()).postDelayed({ + stopSoundOnPebbleBeeDevice(gatt) + }, 5000) + } + + if (characteristic?.value.contentEquals(PEBBLEBEE_STOP_SOUND_OPCODE)) { + disconnect(gatt) + broadcastUpdate(BluetoothConstants.ACTION_EVENT_COMPLETED) + } + + } else { + Timber.d("Writing to characteristic failed ${characteristic?.uuid}") + disconnect(gatt) + broadcastUpdate(BluetoothConstants.ACTION_EVENT_FAILED) + } + super.onCharacteristicWrite(gatt, characteristic, status) + } + } + + companion object : DeviceContext { + internal const val PEBBLEBEE_SOUND_SERVICE = "FA25" + internal val PEBBLEBEE_SOUND_CHARACTERISTIC = UUID.fromString("00002C02-0000-1000-8000-00805f9b34fb") + internal val PEBBLEBEE_START_SOUND_OPCODE = byteArrayOf(0x01) + internal val PEBBLEBEE_STOP_SOUND_OPCODE = byteArrayOf(0x02) + + override val bluetoothFilter: ScanFilter + get() = ScanFilter.Builder() + .setServiceUuid(offlineFindingServiceUUID) + .build() + + override val deviceType: DeviceType + get() = DeviceType.PEBBLEBEE + + override val defaultDeviceName: String + get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.pebblebee_default_name) + + override val websiteManufacturer: String + get() = "https://pebblebee.com/" + + override val statusByteDeviceType: UInt + get() = 0u + + val offlineFindingServiceUUID: ParcelUuid = ParcelUuid.fromString("0000FA25-0000-1000-8000-00805F9B34FB") + } +} \ No newline at end of file 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 187413d9..a1fe3af3 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 @@ -33,7 +33,10 @@ class Tile(val id: Int) : Device(){ get() = DeviceType.TILE override val defaultDeviceName: String - get() = "Tile" + get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.tile_default_name) + + override val websiteManufacturer: String + get() = "https://www.tile.com/" override val websiteManufacturer: String get() = "https://www.tile.com/" diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/detection/BackgroundBluetoothScanner.kt b/app/src/main/java/de/seemoo/at_tracking_detection/detection/BackgroundBluetoothScanner.kt index 7725ce24..60b0ac8d 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/detection/BackgroundBluetoothScanner.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/detection/BackgroundBluetoothScanner.kt @@ -87,15 +87,14 @@ object BackgroundBluetoothScanner { return BackgroundScanResults(0, 0, 0, true) } try { - val bluetoothManager = - applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + val bluetoothManager = applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothAdapter = bluetoothManager.adapter - if (bluetoothAdapter.bluetoothLeScanner == null) { - Timber.e("BluetoothLeScanner not found!") + if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled || bluetoothAdapter.bluetoothLeScanner == null) { + Timber.e("Bluetooth is disabled or BLE is not supported on this device.") return BackgroundScanResults(0, 0, 0, true) } } catch (e: Throwable) { - Timber.e("BluetoothAdapter not found!") + Timber.e("BluetoothAdapter not found or BLE not supported!") return BackgroundScanResults(0, 0, 0, true) } @@ -126,10 +125,9 @@ object BackgroundBluetoothScanner { } } - //Starting BLE Scan + // Starting BLE Scan Timber.d("Start Scanning for bluetooth le devices...") - val scanSettings = - ScanSettings.Builder().setScanMode(scanMode).build() + val scanSettings = ScanSettings.Builder().setScanMode(scanMode).build() SharedPrefs.isScanningInBackground = true BLEScanCallback.startScanning(bluetoothAdapter.bluetoothLeScanner, DeviceManager.scanFilter, scanSettings, leScanCallback) diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/detection/LocationProvider.kt b/app/src/main/java/de/seemoo/at_tracking_detection/detection/LocationProvider.kt index f87df307..0d0817df 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/detection/LocationProvider.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/detection/LocationProvider.kt @@ -2,14 +2,13 @@ package de.seemoo.at_tracking_detection.detection import android.Manifest import android.content.Context -import android.content.Intent import android.content.pm.PackageManager import android.location.Location import android.location.LocationListener import android.location.LocationManager +import android.os.Build import android.os.Handler import android.os.Looper -import android.provider.Settings import androidx.core.content.ContextCompat import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication import timber.log.Timber @@ -20,23 +19,18 @@ import javax.inject.Singleton @Singleton open class LocationProvider @Inject constructor( - private val locationManager: LocationManager): LocationListener { + private val locationManager: LocationManager +) : LocationListener { private val handler: Handler = Handler(Looper.getMainLooper()) private var bestLastLocation: Location? = null - private val locationRequesters = ArrayList() fun getLastLocation(checkRequirements: Boolean = true): Location? { if (ContextCompat.checkSelfPermission( ATTrackingDetectionApplication.getAppContext(), Manifest.permission.ACCESS_FINE_LOCATION - ) != PackageManager.PERMISSION_GRANTED && - ContextCompat.checkSelfPermission( - ATTrackingDetectionApplication.getAppContext(), - Manifest.permission.ACCESS_COARSE_LOCATION - ) != PackageManager.PERMISSION_GRANTED - ) { + ) != PackageManager.PERMISSION_GRANTED) { return null } @@ -51,10 +45,6 @@ open class LocationProvider @Inject constructor( if (ContextCompat.checkSelfPermission( ATTrackingDetectionApplication.getAppContext(), Manifest.permission.ACCESS_FINE_LOCATION - ) != PackageManager.PERMISSION_GRANTED && - ContextCompat.checkSelfPermission( - ATTrackingDetectionApplication.getAppContext(), - Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return null @@ -65,15 +55,12 @@ open class LocationProvider @Inject constructor( return bestLocation } - // The fused location provider does not work reliably with Samsung + Android 12 - // We just stay with the legacy location, because this just works val lastLocation = legacyGetLastLocationFromAnyProvider(checkRequirements) if (lastLocation != null && bestLocation != null && !checkRequirements) { - if (lastLocation.time > bestLocation.time) { - return lastLocation - } - return bestLocation + return if (lastLocation.time > bestLocation.time) { + lastLocation + } else bestLocation } return lastLocation } @@ -83,10 +70,6 @@ open class LocationProvider @Inject constructor( if (ContextCompat.checkSelfPermission( ATTrackingDetectionApplication.getAppContext(), Manifest.permission.ACCESS_FINE_LOCATION - ) != PackageManager.PERMISSION_GRANTED && - ContextCompat.checkSelfPermission( - ATTrackingDetectionApplication.getAppContext(), - Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return null @@ -133,10 +116,10 @@ open class LocationProvider @Inject constructor( if (location.accuracy <= MIN_ACCURACY_METER) { if (getSecondsSinceLocation(location) <= MAX_AGE_SECONDS) { return true - }else { + } else { Timber.d("Location too old") } - }else { + } else { Timber.d("Location accuracy is not good enough") } return false @@ -272,11 +255,11 @@ open class LocationProvider @Inject constructor( val bestLastLocation = this.bestLastLocation if (bestLastLocation == null) { this.bestLastLocation = location - }else { + } else { if (bestLastLocation.time - location.time > MAX_AGE_SECONDS * 1000L) { // Current location is newer update this.bestLastLocation = location - }else if (bestLastLocation.accuracy > location.accuracy) { + } else if (bestLastLocation.accuracy > location.accuracy) { this.bestLastLocation = location } } @@ -288,7 +271,7 @@ open class LocationProvider @Inject constructor( locationRequester.receivedAccurateLocationUpdate(location) } this.locationRequesters.clear() - }else { + } else { Timber.d("New location does not satisfy requirements. Waiting for a better one") } } @@ -307,11 +290,14 @@ open class LocationProvider @Inject constructor( fun isLocationTurnedOn(): Boolean { val context = ATTrackingDetectionApplication.getAppContext() val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager - return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled( - LocationManager.FUSED_PROVIDER) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled( + LocationManager.FUSED_PROVIDER) + } else { + locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + } } } - } abstract class LocationRequester { 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 ca9c1268..4794d1a1 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 @@ -67,6 +67,14 @@ class TrackingDetectorWorker @AssistedInject constructor( } Timber.d("Tracking detector worker finished. Sent $notificationsSent notifications") + + Timber.d("Deleting old trackers") + try { + deleteOldAndSafeTrackers() + }catch (e:Exception) { + Timber.e("Deleting trackers failed ${e}") + } + return Result.success( Data.Builder() .putInt("sentNotifications", notificationsSent) diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/notifications/NotificationBuilder.kt b/app/src/main/java/de/seemoo/at_tracking_detection/notifications/NotificationBuilder.kt index e4b2e0d9..d370f3ec 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/notifications/NotificationBuilder.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/notifications/NotificationBuilder.kt @@ -51,25 +51,25 @@ class NotificationBuilder @Inject constructor( } - private fun pendingIntentMainActivity(): PendingIntent { - val intent = Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - action = NotificationConstants.CLICKED_ACTION - } - - val context = ATTrackingDetectionApplication.getCurrentActivity() ?: ATTrackingDetectionApplication.getAppContext() - val resultPendingIntent: PendingIntent = TaskStackBuilder.create(context).run { - addNextIntentWithParentStack(intent) - var flags = PendingIntent.FLAG_UPDATE_CURRENT - // For S+ the FLAG_IMMUTABLE or FLAG_MUTABLE must be set - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - flags = flags or PendingIntent.FLAG_IMMUTABLE - } - getPendingIntent(1654, flags) - } - return resultPendingIntent - - } +// private fun pendingIntentMainActivity(): PendingIntent { +// val intent = Intent(context, MainActivity::class.java).apply { +// flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK +// action = NotificationConstants.CLICKED_ACTION +// } +// +// val context = ATTrackingDetectionApplication.getCurrentActivity() ?: ATTrackingDetectionApplication.getAppContext() +// val resultPendingIntent: PendingIntent = TaskStackBuilder.create(context).run { +// addNextIntentWithParentStack(intent) +// var flags = PendingIntent.FLAG_UPDATE_CURRENT +// // For S+ the FLAG_IMMUTABLE or FLAG_MUTABLE must be set +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { +// flags = flags or PendingIntent.FLAG_IMMUTABLE +// } +// getPendingIntent(1654, flags) +// } +// return resultPendingIntent +// +// } private fun packBundle(deviceAddress: String, notificationId: Int): Bundle = Bundle().apply { diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/notifications/NotificationService.kt b/app/src/main/java/de/seemoo/at_tracking_detection/notifications/NotificationService.kt index 91c01e9c..a30eeb67 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/notifications/NotificationService.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/notifications/NotificationService.kt @@ -17,7 +17,6 @@ import de.seemoo.at_tracking_detection.util.SharedPrefs import timber.log.Timber import java.time.LocalDateTime import java.time.temporal.ChronoUnit -import java.util.UUID import javax.inject.Inject import javax.inject.Singleton import kotlin.random.Random @@ -30,7 +29,8 @@ class NotificationService @Inject constructor( ) { @SuppressLint("MissingPermission") suspend fun sendTrackingNotification(deviceAddress: String) { - val notificationId = notificationViewModel.insert(deviceAddress) + val notificationId = generateUniqueNotificationId() + notificationViewModel.insert(deviceAddress) with(notificationManagerCompat) { if (this.areNotificationsEnabled()) { notify( @@ -44,7 +44,8 @@ class NotificationService @Inject constructor( @SuppressLint("MissingPermission") suspend fun sendTrackingNotification(baseDevice: BaseDevice) { - val notificationId = notificationViewModel.insert(deviceAddress = baseDevice.address) + val notificationId = generateUniqueNotificationId() + notificationViewModel.insert(deviceAddress = baseDevice.address) with(notificationManagerCompat) { if (this.areNotificationsEnabled()) { notify( @@ -58,7 +59,7 @@ class NotificationService @Inject constructor( @SuppressLint("MissingPermission") fun sendObserveTrackerNotification(deviceAddress: String, observationDuration: Long, observationPositive: Boolean) { - val notificationId = generateNotificationId() + val notificationId = generateUniqueNotificationId() with(notificationManagerCompat) { if (this.areNotificationsEnabled()) { notify( @@ -72,7 +73,7 @@ class NotificationService @Inject constructor( @SuppressLint("MissingPermission") fun sendObserveTrackerFailedNotification() { - val notificationId = generateNotificationId() + val notificationId = generateUniqueNotificationId() with(notificationManagerCompat) { if (this.areNotificationsEnabled()) { notify( @@ -129,11 +130,12 @@ class NotificationService @Inject constructor( @SuppressLint("MissingPermission") fun sendDebugNotificationFoundDevice(scanResult: ScanResult) { + val notificationId = generateUniqueNotificationId() with(notificationManagerCompat) { if (this.areNotificationsEnabled()) { notify( BLE_SCAN_ERROR_TAG, - Random.nextInt(), + notificationId, notificationBuilder.buildDebugFoundDeviceNotification(scanResult) ) } @@ -207,8 +209,8 @@ class NotificationService @Inject constructor( "de.seemoo.at_tracking_detection.observe_tracker_notification" // const val SURVEY_INFO_TAG = "de.seemoo.at_tracking_detection.survey_info" - fun generateNotificationId(): Int { - return UUID.randomUUID().hashCode() + fun generateUniqueNotificationId(): Int { + return Random.nextInt() } } } \ No newline at end of file diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/AllDevicesFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/AllDevicesFragment.kt index 30e4b931..b2040fbd 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/AllDevicesFragment.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/AllDevicesFragment.kt @@ -89,9 +89,19 @@ class AllDevicesFragment : Fragment() { findNavController().navigate(directions) } + view.findViewById(R.id.pebblebees_found_card).setOnClickListener { + val directions = AllDevicesFragmentDirections.actionNavigationAllDevicesFragmentToDevicesFound2(showDevicesFound = true, showAllDevices = true, deviceType = DeviceType.PEBBLEBEE) + findNavController().navigate(directions) + } + view.findViewById(R.id.smarttags_found_card).setOnClickListener { val directions = AllDevicesFragmentDirections.actionNavigationAllDevicesFragmentToDevicesFound2(showDevicesFound = true, showAllDevices = true, deviceType = DeviceType.GALAXY_SMART_TAG, deviceType2 = DeviceType.GALAXY_SMART_TAG_PLUS) findNavController().navigate(directions) } + + view.findViewById(R.id.google_found_card).setOnClickListener { + val directions = AllDevicesFragmentDirections.actionNavigationAllDevicesFragmentToDevicesFound2(showDevicesFound = true, showAllDevices = true, deviceType = DeviceType.GOOGLE_FIND_MY_NETWORK) + findNavController().navigate(directions) + } } } \ No newline at end of file diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/AllDevicesViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/AllDevicesViewModel.kt index 09efdb49..dfe5d656 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/AllDevicesViewModel.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/AllDevicesViewModel.kt @@ -26,6 +26,8 @@ class AllDevicesViewModel @Inject constructor( val countFindMy = deviceRepository.countForDeviceType(DeviceType.FIND_MY).asLiveData() val countTile = deviceRepository.countForDeviceType(DeviceType.TILE).asLiveData() val countChipolo = deviceRepository.countForDeviceType(DeviceType.CHIPOLO).asLiveData() + val countGoogle = deviceRepository.countForDeviceType(DeviceType.GOOGLE_FIND_MY_NETWORK).asLiveData() + val countPebblebee = deviceRepository.countForDeviceType(DeviceType.PEBBLEBEE).asLiveData() val countSmartTag = deviceRepository.countForDeviceTypes( DeviceType.GALAXY_SMART_TAG, DeviceType.GALAXY_SMART_TAG_PLUS ).asLiveData() diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanViewModel.kt index e63f66c6..a7890380 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanViewModel.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanViewModel.kt @@ -107,8 +107,9 @@ class ScanViewModel @Inject constructor( bluetoothDeviceListLowRiskValue.add(wrappedScanResult) } - bluetoothDeviceListHighRiskValue.sortByDescending { it.rssiValue } - bluetoothDeviceListLowRiskValue.sortByDescending { it.rssiValue } + // Sorting list by detection date is not so restless +// bluetoothDeviceListHighRiskValue.sortByDescending { it.rssiValue } +// bluetoothDeviceListLowRiskValue.sortByDescending { it.rssiValue } bluetoothDeviceListHighRisk.postValue(bluetoothDeviceListHighRiskValue) bluetoothDeviceListLowRisk.postValue(bluetoothDeviceListLowRiskValue) diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/DataDeletionFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/DataDeletionFragment.kt index d223c8f4..9d6b25e5 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/DataDeletionFragment.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/DataDeletionFragment.kt @@ -43,14 +43,16 @@ class DataDeletionFragment : Fragment() { val token = SharedPrefs.token CoroutineScope(Dispatchers.Main).launch { + val rootView = requireView() // Get the root view of the fragment + if (!api.ping().isSuccessful) { Timber.e("Server not available!") val text = R.string.delete_data_server_error - Snackbar.make(view, text, Snackbar.LENGTH_LONG).show() + Snackbar.make(rootView, text, Snackbar.LENGTH_LONG).show() // Use rootView } else if (token == null) { Timber.e("Token is null! Could not delete data!") val text = R.string.delete_data_no_data - Snackbar.make(view, text, Snackbar.LENGTH_LONG).show() + Snackbar.make(rootView, text, Snackbar.LENGTH_LONG).show() // Use rootView } else { val response = api.deleteStudyData(token) @@ -59,18 +61,15 @@ class DataDeletionFragment : Fragment() { SharedPrefs.token = null sharedPreferences.edit().putBoolean("share_data", false).apply() val text = R.string.delete_data_success - Snackbar.make(view, text, Snackbar.LENGTH_LONG).show() + Snackbar.make(rootView, text, Snackbar.LENGTH_LONG).show() // Use rootView findNavController().popBackStack() } else { Timber.e("Data Deletion Failed! Server sent error!") val text = R.string.delete_data_error - Snackbar.make(view, text, Snackbar.LENGTH_LONG).show() + Snackbar.make(rootView, text, Snackbar.LENGTH_LONG).show() // Use rootView } } } - - } } - } \ No newline at end of file diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/SharedPrefs.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/SharedPrefs.kt index 864484e7..ef61c42b 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/util/SharedPrefs.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/SharedPrefs.kt @@ -12,6 +12,34 @@ object SharedPrefs { private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(ATTrackingDetectionApplication.getAppContext()) + init { + // Migrate old devices filter to new format + migrateDevicesFilter() + } + + private fun migrateDevicesFilter() { + val oldDevicesFilterKey = "devices_filter" + val newDevicesFilterKey = "devices_filter_unselected" + if (sharedPreferences.contains(oldDevicesFilterKey) && !sharedPreferences.contains(newDevicesFilterKey)) { + var oldSelectedOptions = sharedPreferences.getStringSet(oldDevicesFilterKey, emptySet()) ?: emptySet() + + val googleFindMyNetworkValue = ATTrackingDetectionApplication.getAppContext().resources.getStringArray(R.array.devicesFilterValue).find { it == "google_find_my_network" } + googleFindMyNetworkValue?.let { + oldSelectedOptions = oldSelectedOptions + it + } + + val pebbleBeeValue = ATTrackingDetectionApplication.getAppContext().resources.getStringArray(R.array.devicesFilterValue).find { it == "pebblebees" } + pebbleBeeValue?.let { + oldSelectedOptions = oldSelectedOptions + it + } + + val allOptions = getAllDevicesFilterOptions() + val newUnselectedOptions = allOptions - oldSelectedOptions + sharedPreferences.edit().putStringSet(newDevicesFilterKey, newUnselectedOptions).apply() + sharedPreferences.edit().remove(oldDevicesFilterKey).apply() + } + } + var isScanningInBackground: Boolean get() { return sharedPreferences.getBoolean("isScanningInBackground", false) @@ -194,14 +222,15 @@ object SharedPrefs { } var devicesFilter: Set - // 0: Low - // 1: Medium - // 2: High get() { - return sharedPreferences.getStringSet("devices_filter", getDefaultDevicesFilterSet())?:getDefaultDevicesFilterSet() + val allOptions = getAllDevicesFilterOptions() + val selectedOptions = sharedPreferences.getStringSet("devices_filter_unselected", emptySet())?: emptySet() + return allOptions - selectedOptions } set(value) { - sharedPreferences.edit().putStringSet("devices_filter", value).apply() + val allOptions = getAllDevicesFilterOptions() + val unselectedOptions = allOptions - value + sharedPreferences.edit().putStringSet("devices_filter_unselected", unselectedOptions).apply() } var notificationPriorityHigh: Boolean @@ -212,8 +241,8 @@ object SharedPrefs { sharedPreferences.edit().putBoolean("notification_priority_high", value).apply() } - private fun getDefaultDevicesFilterSet(): Set { - val defaultValues = ATTrackingDetectionApplication.getAppContext().resources.getStringArray(R.array.devicesFilterValue) - return defaultValues.toSet() + private fun getAllDevicesFilterOptions(): Set { + val allOptions = ATTrackingDetectionApplication.getAppContext().resources.getStringArray(R.array.devicesFilterValue) + return allOptions.toSet() } } \ No newline at end of file diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/Utility.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/Utility.kt index a1e2c4e1..74792cda 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/util/Utility.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/Utility.kt @@ -249,6 +249,7 @@ object Utility { DeviceType.GALAXY_SMART_TAG_PLUS -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.explanation_samsung) DeviceType.TILE -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.explanation_tile) DeviceType.CHIPOLO -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.explanation_chipolo) + DeviceType.PEBBLEBEE -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.explanation_pebblebee) else -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.explanation_unknown) } } diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/BLEScanner.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/BLEScanner.kt index 469ac160..196e598a 100644 --- a/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/BLEScanner.kt +++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/BLEScanner.kt @@ -11,6 +11,7 @@ import android.location.Location import android.provider.Settings import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication import de.seemoo.at_tracking_detection.database.models.device.DeviceManager +import de.seemoo.at_tracking_detection.database.models.device.types.GoogleFindMyNetwork import de.seemoo.at_tracking_detection.detection.LocationRequester import de.seemoo.at_tracking_detection.util.Utility import timber.log.Timber @@ -100,8 +101,7 @@ object BLEScanner { super.onScanResult(callbackType, result) // TODO: Add scan result to DB here. Detection events should not be to close after each other. // New detection events (Beacons) every 15min -// Timber.d("Found a device $result") - + // Timber.d("Found a device $result") result?.let { scanResult -> scanResults.add(0, scanResult) if (scanResults.size > 10) { diff --git a/app/src/main/res/layout/fragment_all_devices.xml b/app/src/main/res/layout/fragment_all_devices.xml index ec7048be..c497824a 100644 --- a/app/src/main/res/layout/fragment_all_devices.xml +++ b/app/src/main/res/layout/fragment_all_devices.xml @@ -111,13 +111,13 @@ android:orientation="horizontal"> + + + + + + + + + - + /> + diff --git a/app/src/main/res/navigation/tracking_navigation.xml b/app/src/main/res/navigation/tracking_navigation.xml index a171404c..8784d54d 100644 --- a/app/src/main/res/navigation/tracking_navigation.xml +++ b/app/src/main/res/navigation/tracking_navigation.xml @@ -25,6 +25,7 @@ app:argType="string" app:nullable="true" /> + Tracking-Erkennung Warnt dich vor "Find My"-Geräten, die dich evtl. verfolgen Sobald AirGuard einen AirTag oder einen anderen Tracker in Deiner Nähe findet, wird es hier aufgelistet. Die Liste wird daher auch Geräte enthalten, die zufällig in Deiner Umgebung waren und dich nicht verfolgen. Du erhältst eine Benachrichtigung für Geräte, die dich verfolgen. - Immer wenn Du eine Benachrichtigung erhalten hast, dass Du von einem Dir bekannten Gerät (z.B. einem Tile, den Du besitzt) verfolgt wirst, dann kannst Du dies als Fehlalarm kennzeichnen. Geräte, die ihre Bluetooth-Adresse nicht ändern (z.B. Tile oder Chipolo) kannst Du auch ignorieren. Diese ignorierten Geräte werden hier angezeigt. Denke daran: Ein AirTag ändert seine Bluetooth-Adresse einmal am Tag, kann also nicht ignoriert werden und wird am nächsten Tag als neuer AirTag angezeigt. + Immer wenn Du eine Benachrichtigung erhalten hast, dass Du von einem Dir bekannten Gerät (z.B. einem Tile, den Du besitzt) verfolgt wirst, dann kannst Du dies als Fehlalarm kennzeichnen. Geräte, die ihre Bluetooth-Adresse nicht ändern (z.B. Pebblebee, Tile oder Chipolo) kannst Du auch ignorieren. Diese ignorierten Geräte werden hier angezeigt. Denke daran: Ein AirTag ändert seine Bluetooth-Adresse einmal am Tag, kann also nicht ignoriert werden und wird am nächsten Tag als neuer AirTag angezeigt. Aktivieren, um detailliertere Standortinformationen bei einer Warnung zu erhalten Standort nutzen @@ -35,7 +35,7 @@ Ton abspielen AirTag lokalisieren Standort im Hintergrund - Wenn Du diese Berechtigung erteilst, kannst Du detailliertere Standortinformationen zu einem AirTag, Find My-Gerät, SmartTag, Chipolo oder Tile erhalten, welches dich verfolgt. Diese Berechtigung ist optional. + Wenn Du diese Berechtigung erteilst, kannst Du detailliertere Standortinformationen zu einem AirTag, Find My-Gerät, SmartTag, Google Mein Gerät finden-Gerät, Pebblebee, Chipolo oder Tile erhalten, welches dich verfolgt. Diese Berechtigung ist optional. OK Feedback Mehr Informationen mitteilen @@ -66,7 +66,7 @@ - Wir werden einige Informationen über die entdeckten Geräte sammeln, wie den Gerätetyp, den RSSI-Wert (Signalstärke) und das Datum und die Uhrzeit der Entdeckung \n\n - Wie viele Benachrichtigungen angezeigt wurden, das Datum und die Uhrzeit der Benachrichtigung sowie das Benutzerfeedback, das gegeben werden kann \n\n Wir werden KEINE Standorte oder Daten teilen, die es uns ermöglichen könnten, dich zu identifizieren - AirGuard benachrichtigt dich, falls Du von einem Airtag, Find My-Gerät, SmartTag, Chipolo oder Tile verfolgt wirst. + AirGuard benachrichtigt dich, falls Du von einem Airtag, Find My-Gerät, Google "Mein Gerät finden"-Gerät, SmartTag, Pebblebee, Chipolo oder Tile verfolgt wirst. Standortberechtigung AirGuard benötigt die Berechtigung, auf Deinen Standort zuzugreifen, um über Bluetooth Low Energy (LE) nach umliegenden AirTags, SmartTags und Tiles suchen zu dürfen. Du musst diese Berechtigung erteilen, um AirGuard zu verwenden. Batterie-Optimierung @@ -90,9 +90,9 @@ Bekomme Informationen über uns Studiendaten löschen Anfrage der Löschung der Studiendaten - Gerätetypen filtern - Gerätetypen auswählen - Auswählen welche Gerätentypen AirGuard scannen soll. Nicht markierte Gerätetypen tauchen weder im Scan-Tab auf, noch wird im Hintergrund nach ihnen gescannt. + Gerätetypen herausfiltern + Gerätetypen ignorieren + Auswählen welche Gerätentypen AirGuard nicht scannen soll. Markierte Gerätetypen tauchen weder im Scan-Tab auf, noch wird im Hintergrund nach ihnen gescannt. @@ -105,7 +105,8 @@ Nein Ja Bitte entscheide dich, ob du an unserer Studie teilnimmst oder nicht. - "Find My"-Gerät %s + Apple "Find My" %s + Google "Mein Gerät finden" %s Datenlöschung anfragen Artikel Es wurden noch keine Geräte gefunden. Jedes Mal, wenn ein Gerät entdeckt wird, wird sein Standort auf der Karte angezeigt. Dies bedeutet nicht, dass jemand dich an jedem Standort geortet hat. In den meisten Fällen wirst Du Geräte von anderen Personen in öffentlichen Verkehrsmitteln oder von einem Deiner Nachbarn finden. @@ -146,7 +147,7 @@ Senden von Benachrichtigungen Um Benachrichtigungen zu senden, wenn ein Gerät gefunden wurde, benötigen wir die Berechtigung Benachrichtigungen zu verschicken. "Scanergebnis Element" - Keine Tracking-Geräte wurden gefunden.\nWir scannen nur nach Apple Find My Trackern (AirTags), Galaxy SmartTags, Chipolo Trackern und Tile Trackern. + Keine Tracking-Geräte wurden gefunden.\nWir scannen nur nach Apple Find My Trackern (AirTags), Google "Mein Gerät finden" Trackern, Galaxy SmartTags, Pebblebee, Chipolo und Tile Trackern. Verbindung zum Gerät fehlgeschlagen! Ja Bluetooth-Suche @@ -253,6 +254,8 @@ Benachrichtigung mit hoher Prioriät Benachrichtigungen werden als Alarm mit hoher Priorität erstellt Gefundene Chipolo Geräte + Gefundene Pebblebee Geräte + Gefundene Google Find My Geräte Bluetooth ist ausgeschaltet. Diese App benötigt Bluetooth, um nach Trackern in der Nähe zu scannen. Standort ist ausgeschaltet. Diese App benötigt die Standort-Berechtigung, um nach Trackern in der Nähe zu scannen. Bitte aktivieren sie Standorte in ihren System-Einstellungen.\n\nEs kann sein, dass die App neu gestartet werden muss, dass der Scan funktioniert. Bluetooth Einstellungen öffnen @@ -296,6 +299,11 @@ Apple Geräte FindMy Geräte Samsung Geräte + Google "Mein Gerät finden" + + Google "Mein Gerät finden" + Apple "FindMy" + Apple Gerät Tracker beobachten Beobachtung stoppen @@ -345,6 +353,7 @@ Hinweis:\nSamsung Geräte ändern ihre Identität mindestens alle 24 Stunden.\nSmartTags und andere Samsung Geräte werden daher periodisch als neue Geräte erkannt und könnten mehrfach angezeigt werden. Hinweis:\nTile Geräte ändern ihre Identität nicht.\nDies kann zu mehr falschen Warnungen führen.\nFalls sie über ein Tile gewarnt werden, überprüfen Sie ob der Tracker von jemanden aus ihrer Umgebung (Freunde, Familie, Kollegen, Nachbarn etc.) stammt. Hinweis:\nChipolo Geräte ändern ihre Identität nicht.\nDies kann zu mehr falschen Warnungen führen.\nFalls sie über einen Chipolo-Tracker gewarnt werden, überprüfen Sie ob der Tracker von jemanden aus ihrer Umgebung (Freunde, Familie, Kollegen, Nachbarn etc.) stammt. + Hinweis:\nPebblebee Geräte ändern ihre Identität nicht.\nDies kann zu mehr falschen Warnungen führen.\nFalls sie über einen Pebblebee-Tracker gewarnt werden, überprüfen Sie ob der Tracker von jemanden aus ihrer Umgebung (Freunde, Familie, Kollegen, Nachbarn etc.) stammt. Hinweis:\nManche Geräte ändern ihre Identität regelmäßig.\nDies kann zu mehr falschen Warnungen führen.\nDiese Geräte werden daher periodisch als neue Geräte erkannt und könnten mehrfach angezeigt werden. Sichere Tracker sind Tracker in ihrer Umgebung, die entweder gerade oder kürzlich mit ihrem Besitzer verbunden waren und somit höchstwahrscheinlich keine Gefahr für Sie darstellen.\nEbenfalls werden alle Tracker, die sie ignorieren als sicher gewertet. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 4971f25e..2f743bc1 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -87,7 +87,7 @@ いいえ はい 匿名化されたデータを共有するかどうかを決定する必要があります - Find My デバイス %s + Find My デバイス %s 詳細については、通知を開いてください。追跡タグが少なくとも%d分間あなたを追跡しています。 追跡タグが発見されました! 詳細については、通知を開いてください。%sが少なくとも%d分間あなたを追跡しています。 diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index 14cffa95..e4ae691e 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -25,17 +25,23 @@ @string/airtag @string/apple_device @string/chipolo + @string/pebblebee @string/find_my_device @string/smart_tag @string/tile + @string/google_find_my airpods airtags apple_devices chipolos + pebblebees find_my_devices smart_tags tiles + google_find_my_network + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a5293651..fbbf1d63 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,7 +35,7 @@ Times seen Swipe to remove Whenever the app detects an AirTag or another tracking device, they will be listed here. The list will likely contain devices that did not track you. For devices that track you, you will receive a notification. - Whenever you receive a notification for being tracked by a known device (e.g. an AirTag you own), you can mark them as a false alarm. Devices which do not change their Bluetooth address (e.g. Tile, Chipolo) can also be ignored. These devices will be listed here. Remember: An AirTag changes its Bluetooth address once a day, so even if you mark it as a false alarm it becomes a new one after a day. + Whenever you receive a notification for being tracked by a known device (e.g. an AirTag you own), you can mark them as a false alarm. Devices which do not change their Bluetooth address (e.g. Tile, Chipolo, Pebblebee) can also be ignored. These devices will be listed here. Remember: An AirTag changes its Bluetooth address once a day, so even if you mark it as a false alarm it becomes a new entry after a day. If enabled, more detailed tracking information can be provided when being tracked. Use Location Error requesting background Location permission @@ -81,7 +81,7 @@ We will NOT share any location data or data which might identify you! The participation is completely anonymous. - This App will notify you when it seems like an AirTag, Find My capable device, SmartTag, Chipolo or a Tile is tracking you. + This App will notify you when it seems like an AirTag, Find My capable device, Google "Find My Device" capable device, SmartTag, Pebblebee, Chipolo or a Tile is tracking you. Location Permission This app needs the Location permission to allow Bluetooth scanning for AirTags or other Bluetooth Trackers even when the app is closed or not in use. Additionally, you can optionally allow the app to log the location when you have been tracked. @@ -120,16 +120,17 @@ Delete study data Request deletion of study data - Filter by device types - Choose device types - Choose which device types AirGuard should observe. Not selected device types will not show up in the manual scan and you will not be notified if such a device follows you. + Filter out device types + Ignore device types + Choose which device types AirGuard should ignore. Selected device types will not show up in the manual scan and you will not be notified if such a device follows you. No Yes Please decide if you want to participate or not. All data shared is completely anonymous. AirTag %s - Find My Device %s + Apple "FindMy" %s + Google "Find My Device" %s Your observed tracker @@ -154,7 +155,7 @@ Device icon Location sample image Background Location - This permission is needed to provide some more detailed tracking information in case an AirTag, Find My device, SmartTag, Chipolo or Tile is tracking you. This permission is optional. + This permission is needed to provide some more detailed tracking information in case an AirTag, Find My device, Samsung SmartTag, Google Find My Device, Pebblebee, Chipolo or Tile is tracking you. This permission is optional. Location Permission This app needs permission to access your location to search for surrounding Trackers via Bluetooth Low Energy. Without this permission, the app does not work and will not find any tracking devices. Your location data never leaves your device. Edit device name @@ -176,7 +177,7 @@ Check out our Twitter account Twitter Scan Result Item - No tracking devices were found.\nWe only scan for Apple Find My Trackers (AirTags), Galaxy SmartTags, Chipolos and Tiles. + No tracking devices were found.\nWe only scan for Apple Find My Trackers (AirTags), Google "Find My Device" Devices and Galaxy SmartTags, Pebblebees, Chipolos and Tiles. Failed to connect to device Yes Bluetooth must be turned on to scan for bluetooth devices. @@ -244,6 +245,7 @@ Beacon locations Other devices found Tile %s + Pebblebee %s Samsung Device %s SmartTag %s SmartTag Plus %s @@ -263,10 +265,12 @@ Low Power Scan Mode Ignored devices AirTags found - Find My Devices found + Apple Find My Devices found Tiles found Chipolos found + Pebblebees found SmartTags found + Google Find My Devices found We are doing a survey We are trying to find out how many people are experiencing stalking and location tracking attacks. We invite every user to participate in the survey, even if you are not a victim to any of these attacks. This survey is part of our research project at the Technical University of Darmstadt. Participate @@ -329,15 +333,27 @@ Airtag Airpods Apple Devices - Chipolo - FindMy Devices + Chipolos + Pebblebees + Apple FindMy Devices Samsung Devices SmartTags Tiles + Google "Find My Device" Devices AirGuard for Android Version %s Contact Us + Google "Find My Device" + Apple "FindMy" + Apple Device + AirTag + AirPods + Chipolo + Tile + Pebblebee + + Preview Image for this article Show explanation text @@ -377,6 +393,7 @@ Please note:\nSamsung devices change their identifier over time.\nSmartTags and other Samsung SmartThings devices are recognized as new devices periodically and can be displayed multiple times. Please note:\nTile devices do not change their identifier.\nThis can lead to false warnings.\nIn case of a warning, please check if the device is not carried by someone around you (friends, family, colleges, neighbours etc.). Please note:\nChipolo devices do not change their identifier.\nThis can lead to false warnings.\nIn case of a warning, please check if the device is not carried by someone around you (friends, family, colleges, neighbours etc.). + Please note:\nPebblebee devices do not change their identifier.\nThis can lead to false warnings.\nIn case of a warning, please check if the device is not carried by someone around you (friends, family, colleges, neighbours etc.). Please note:\nSome devices change their identifier over time.\nThese devices are recognized as new devices periodically and can be displayed multiple times. Safe Trackers are trackers around you that are currently connected or have been connected to their owner devices in the last minutes. They most likely do not pose a threat to you.\nAdditionally all tracker marked as ignored will be considered to be safe. diff --git a/app/src/main/res/xml/fragment_settings.xml b/app/src/main/res/xml/fragment_settings.xml index 986fba24..15820788 100644 --- a/app/src/main/res/xml/fragment_settings.xml +++ b/app/src/main/res/xml/fragment_settings.xml @@ -45,14 +45,14 @@ android:title="@string/settings_risk_sensitivity_title" />