From 1c95950002c13370db7c2ed3e579cf68d86c9b1d Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 24 Sep 2023 12:59:51 +0200
Subject: [PATCH 001/154] improve LocationProvider, can handle COARSE_LOCATION,
works with any Location Provider, improve readability and efficiency
---
.../detection/LocationProvider.kt | 197 +++++++++---------
.../util/ble/OpportunisticBLEScanner.kt | 44 ++--
2 files changed, 112 insertions(+), 129 deletions(-)
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 35412bd4..fe5eb3a9 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
@@ -1,7 +1,6 @@
package de.seemoo.at_tracking_detection.detection
import android.Manifest
-import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
@@ -24,8 +23,16 @@ open class LocationProvider @Inject constructor(
private val locationRequesters = ArrayList()
- open fun getLastLocation(checkRequirements: Boolean = true): Location? {
- if (ContextCompat.checkSelfPermission(ATTrackingDetectionApplication.getAppContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ 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
+ ) {
return null
}
@@ -33,12 +40,19 @@ open class LocationProvider @Inject constructor(
}
/**
- * Fetches the most recent location from network and gps and returns the one that has been recveived more recently
+ * Fetches the most recent location from network and gps and returns the one that has been received more recently
* @return the most recent location across multiple providers
*/
- @SuppressLint("InlinedApi") // Suppressed, because we use a custom version provider which is injectable for testing
private fun getLastLocationFromAnyProvider(checkRequirements: Boolean): Location? {
- if (ContextCompat.checkSelfPermission(ATTrackingDetectionApplication.getAppContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ 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
}
@@ -61,53 +75,46 @@ open class LocationProvider @Inject constructor(
}
private fun legacyGetLastLocationFromAnyProvider(checkRequirements: Boolean): Location? {
- if (ContextCompat.checkSelfPermission(ATTrackingDetectionApplication.getAppContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ // Check for location permission
+ 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
}
- // On older versions we use both providers to get the best location signal
+ // Get the last known locations from both providers
val networkLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
+ val gpsLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
- if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
- val gpsLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
-
- if (gpsLocation != null && networkLocation != null) {
- // Got to past locations, lets check which passes our requirements
- val gpsRequirements = locationMatchesMinimumRequirements(gpsLocation)
- val networkRequirements = locationMatchesMinimumRequirements(networkLocation)
- if (gpsRequirements && networkRequirements) {
- // Check which one is more current
- if (gpsLocation.time > networkLocation.time) {
- return gpsLocation
- }else {
- return networkLocation
- }
- }else if (gpsRequirements) {
- // Only GPS satisfies the requirements. Return it
- return gpsLocation
- }else if (networkRequirements) {
- // Only network satisfies. Return it
- return networkLocation
- }else if (!checkRequirements) {
- if (gpsLocation.time > networkLocation.time) {
- return gpsLocation
- }
- return networkLocation
- }
- }else if (gpsLocation != null && locationMatchesMinimumRequirements(gpsLocation)) {
- // Only gps satisfies and network does not exist
- return gpsLocation
+ // If both locations are available, return the one that is more current and meets the minimum requirements
+ if (networkLocation != null && gpsLocation != null) {
+ val bestLocation = if (gpsLocation.time > networkLocation.time) gpsLocation else networkLocation
+ if (locationMatchesMinimumRequirements(bestLocation)) {
+ return bestLocation
}
}
+ // If only one location is available, return it if it meets the minimum requirements
if (networkLocation != null && locationMatchesMinimumRequirements(networkLocation)) {
return networkLocation
- }else if (!checkRequirements) {
- return networkLocation
+ }
+ if (gpsLocation != null && locationMatchesMinimumRequirements(gpsLocation)) {
+ return gpsLocation
}
- Timber.d("No last know location matched the requirements")
- return null
+ // If neither location meets the minimum requirements, return null
+ if (checkRequirements) {
+ return null
+ }
+
+ // If no location requirements are specified, return the last known location from either provider, or null if none are available
+ return networkLocation ?: gpsLocation
}
private fun getSecondsSinceLocation(location: Location): Long {
@@ -130,27 +137,38 @@ open class LocationProvider @Inject constructor(
* @param timeoutMillis: After the timeout the last location will be returned no matter if it matches the requirements or not
* @return the last known location if this already satisfies our requirements
*/
- @SuppressLint("InlinedApi") // Suppressed, because we use a custom version provider which is injectable for testing
- open fun lastKnownOrRequestLocationUpdates(locationRequester: LocationRequester, timeoutMillis: Long?): Location? {
- if (ContextCompat.checkSelfPermission(ATTrackingDetectionApplication.getAppContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ open fun lastKnownOrRequestLocationUpdates(
+ locationRequester: LocationRequester,
+ timeoutMillis: Long? = null
+ ): Location? {
+ // Check for location permission
+ if (ContextCompat.checkSelfPermission(
+ ATTrackingDetectionApplication.getAppContext(),
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED) {
return null
}
+ // Get the last known location
val lastLocation = getLastLocation()
+
+ // If the last location is available and meets the minimum requirements, return it
if (lastLocation != null && locationMatchesMinimumRequirements(lastLocation)) {
return lastLocation
}
+ // Add the location requester to the list of active requesters
this.locationRequesters.add(locationRequester)
- // The fused location provider does not work reliably with Samsung + Android 12
- // We just stay with the legacy location, because this just works
+ // Request location updates from all enabled providers
requestLocationUpdatesFromAnyProvider()
+ // If a timeout is specified, set a timeout for the location update
if (timeoutMillis != null) {
- setTimeoutForLocationUpdate(requester = locationRequester, timeoutMillis= timeoutMillis)
+ setTimeoutForLocationUpdate(requester = locationRequester, timeoutMillis = timeoutMillis)
}
+ // Return null, since we don't have a location immediately available
return null
}
@@ -162,45 +180,58 @@ open class LocationProvider @Inject constructor(
* @param timeoutMillis milliseconds after which the timeout will be executed
*/
private fun setTimeoutForLocationUpdate(requester: LocationRequester, timeoutMillis: Long) {
- val handler = Handler(Looper.getMainLooper())
-
- val runnable = kotlinx.coroutines.Runnable {
- if (this@LocationProvider.locationRequesters.size == 0) {
- // The location was already returned
+ // Create a runnable to handle the timeout
+ val runnable = Runnable {
+ // If the location requester list is empty, the location has already been returned
+ if (this@LocationProvider.locationRequesters.isEmpty()) {
return@Runnable
}
+ // Log the timeout and get the last known location, regardless of whether it meets the requirements
Timber.d("Location request timed out")
val lastLocation = this@LocationProvider.getLastLocation(checkRequirements = false)
+
+ // If the last location is available, notify the requester
lastLocation?.let {
- requester.receivedAccurateLocationUpdate(location = lastLocation)
+ requester.receivedAccurateLocationUpdate(location = it)
}
+
+ // If there is only one requester left, stop location updates and clear the list
if (this@LocationProvider.locationRequesters.size == 1) {
this@LocationProvider.stopLocationUpdates()
this@LocationProvider.locationRequesters.clear()
- }else {
+ } else {
+ // Otherwise, remove the requester from the list
this@LocationProvider.locationRequesters.remove(requester)
}
}
+ // Schedule the runnable to be executed after the timeout period
+ val handler = Handler(Looper.getMainLooper())
handler.postDelayed(runnable, timeoutMillis)
+
+ // Log the timeout settings
Timber.d("Location request timeout set to $timeoutMillis")
}
-
private fun requestLocationUpdatesFromAnyProvider() {
- if (ContextCompat.checkSelfPermission(ATTrackingDetectionApplication.getAppContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ // Check for location permission
+ if (ContextCompat.checkSelfPermission(
+ ATTrackingDetectionApplication.getAppContext(),
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
return
}
- Timber.d("Requesting location updates")
- val gpsProviderEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
- val networkProviderEnabled =
- locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
+ // Get the list of enabled location providers
+ val enabledProviders = locationManager.allProviders
+ .filter { locationManager.isProviderEnabled(it) }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && locationManager.isProviderEnabled(LocationManager.FUSED_PROVIDER)) {
+ // Request location updates from all enabled providers
+ enabledProviders.forEach {
locationManager.requestLocationUpdates(
- LocationManager.FUSED_PROVIDER,
+ it,
MIN_UPDATE_TIME_MS,
MIN_DISTANCE_METER,
this,
@@ -208,43 +239,14 @@ open class LocationProvider @Inject constructor(
)
}
- if (networkProviderEnabled) {
- locationManager.requestLocationUpdates(
- LocationManager.NETWORK_PROVIDER,
- MIN_UPDATE_TIME_MS,
- MIN_DISTANCE_METER,
- this,
- handler.looper
- )
- }
-
- if (gpsProviderEnabled) {
- // Using GPS and Network provider, because the GPS provider does notwork indoors (it will never call the callback)
- locationManager.requestLocationUpdates(
- LocationManager.GPS_PROVIDER,
- MIN_UPDATE_TIME_MS,
- MIN_DISTANCE_METER,
- this,
- handler.looper
- )
- }
-
- if (!networkProviderEnabled && !gpsProviderEnabled) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- if (!locationManager.isProviderEnabled(LocationManager.FUSED_PROVIDER)) {
- // Error
- Timber.e("ERROR: No location provider available")
- stopLocationUpdates()
- }
- }else {
- //Error
- Timber.e("ERROR: No location provider available")
- stopLocationUpdates()
- }
+ // If no location providers are enabled, log an error and stop location updates
+ if (enabledProviders.isEmpty()) {
+ Timber.e("ERROR: No location provider available")
+ stopLocationUpdates()
}
}
- fun stopLocationUpdates() {
+ private fun stopLocationUpdates() {
locationManager.removeUpdates(this)
}
@@ -274,9 +276,6 @@ open class LocationProvider @Inject constructor(
}
}
- @Deprecated("Deprecated in Java")
- override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
-
// Android Phones with SDK < 30 need these methods
override fun onProviderEnabled(provider: String) {}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/OpportunisticBLEScanner.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/OpportunisticBLEScanner.kt
index 570015ef..f9f3d5c8 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/OpportunisticBLEScanner.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/OpportunisticBLEScanner.kt
@@ -8,7 +8,6 @@ import android.bluetooth.le.ScanSettings
import android.content.Context
import android.location.Location
import android.location.LocationManager
-import android.os.Build
import android.os.SystemClock
import androidx.core.content.getSystemService
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
@@ -17,7 +16,6 @@ import de.seemoo.at_tracking_detection.database.models.device.DeviceManager
import de.seemoo.at_tracking_detection.detection.LocationProvider
import de.seemoo.at_tracking_detection.detection.LocationRequester
import de.seemoo.at_tracking_detection.notifications.NotificationService
-import de.seemoo.at_tracking_detection.util.DefaultBuildVersionProvider
import de.seemoo.at_tracking_detection.util.SharedPrefs
import de.seemoo.at_tracking_detection.util.Utility
import timber.log.Timber
@@ -39,7 +37,7 @@ class OpportunisticBLEScanner(var notificationService: NotificationService?) {
val context = ATTrackingDetectionApplication.getAppContext()
val locationManager = context.getSystemService()
if (locationManager != null) {
- locationProvider = LocationProvider(locationManager, DefaultBuildVersionProvider())
+ locationProvider = LocationProvider(locationManager)
}
}
@@ -78,27 +76,13 @@ class OpportunisticBLEScanner(var notificationService: NotificationService?) {
this.bluetoothAdapter?.bluetoothLeScanner?.let { BLEScanCallback.stopScanning(it) }
}
- fun scanSettings(): ScanSettings? {
- if (Build.VERSION.SDK_INT >= 23) {
- if (Build.VERSION.SDK_INT >= 26) {
- val scanSettings = ScanSettings.Builder()
- .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC)
- .setLegacy(true)
- .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
- .setReportDelay(0)
- .build()
- return scanSettings
- }
-
- val scanSettings = ScanSettings.Builder()
- .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC)
- .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
-// .setReportDelay(0)
- .build()
- return scanSettings
- }
-
- return null
+ private fun scanSettings(): ScanSettings? {
+ return ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC)
+ .setLegacy(true)
+ .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
+ .setReportDelay(0)
+ .build()
}
private val leScanCallback: ScanCallback = object : ScanCallback() {
@@ -108,10 +92,10 @@ class OpportunisticBLEScanner(var notificationService: NotificationService?) {
if (SharedPrefs.isScanningInBackground) {
Timber.d("Scan received during background scan")
}else {
- Timber.d("Scan outside of background scan ${scanResult}")
+ Timber.d("Scan outside of background scan $scanResult")
scanResult.timestampNanos
- val milisecondsSinceEvent = (SystemClock.elapsedRealtimeNanos() - scanResult.timestampNanos) / 1000000L
- val timeOfEvent = System.currentTimeMillis() - milisecondsSinceEvent
+ val millisecondsSinceEvent = (SystemClock.elapsedRealtimeNanos() - scanResult.timestampNanos) / 1000000L
+ val timeOfEvent = System.currentTimeMillis() - millisecondsSinceEvent
val eventDate = Instant.ofEpochMilli(timeOfEvent).atZone(ZoneId.systemDefault()).toLocalDateTime()
Timber.d("Scan received at ${eventDate.toString()}")
if (BuildConfig.DEBUG) {
@@ -140,8 +124,8 @@ class OpportunisticBLEScanner(var notificationService: NotificationService?) {
if (lastLocation != null) {
val millisecondsSinceLocation = (SystemClock.elapsedRealtimeNanos() - lastLocation.elapsedRealtimeNanos) / 1000000L
- val timeOfLocationevent = System.currentTimeMillis() - millisecondsSinceLocation
- val locationDate = Instant.ofEpochMilli(timeOfLocationevent).atZone(ZoneId.systemDefault()).toLocalDateTime()
+ val timeOfLocationEvent = System.currentTimeMillis() - millisecondsSinceLocation
+ val locationDate = Instant.ofEpochMilli(timeOfLocationEvent).atZone(ZoneId.systemDefault()).toLocalDateTime()
val timeDiff = ChronoUnit.SECONDS.between(locationDate, LocalDateTime.now())
if (timeDiff <= LocationProvider.MAX_AGE_SECONDS && lastLocation.accuracy <= LocationProvider.MIN_ACCURACY_METER) {
@@ -166,7 +150,7 @@ class OpportunisticBLEScanner(var notificationService: NotificationService?) {
}
}
- fun fetchCurrentLocation() {
+ private fun fetchCurrentLocation() {
//Getting the most accurate location here
isUpdatingLocation = true
val loc = locationProvider?.lastKnownOrRequestLocationUpdates(locationRequester, 45_000L)
From 50fc255140ba38775b19b0767dbb41d7dd9a4884 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 27 Sep 2023 11:16:42 +0200
Subject: [PATCH 002/154] small improvements to map rendering
---
.../ui/dashboard/DeviceMapFragment.kt | 3 ---
.../ui/tracking/TrackingFragment.kt | 18 +++++++++---------
app/src/main/res/layout/fragment_tracking.xml | 5 +----
3 files changed, 10 insertions(+), 16 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
index 1463b427..7c4e7226 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
@@ -98,8 +98,5 @@ class DeviceMapFragment : Fragment() {
}
}
}
-
-
}
-
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 064e77a0..fe063e7b 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -55,14 +55,14 @@ class TrackingFragment : Fragment() {
binding.lifecycleOwner = viewLifecycleOwner
binding.vm = trackingViewModel
- val notifId = safeArgs.notificationId
+ val notificationId = safeArgs.notificationId
// This is called deviceAddress but contains the ID
val deviceAddress = safeArgs.deviceAddress
- trackingViewModel.notificationId.postValue(notifId)
+ trackingViewModel.notificationId.postValue(notificationId)
trackingViewModel.deviceAddress.postValue(deviceAddress)
trackingViewModel.loadDevice(safeArgs.deviceAddress)
trackingViewModel.notificationId.observe(viewLifecycleOwner) {
- notificationId = it
+ this.notificationId = it
}
sharedElementEnterTransition =
@@ -106,12 +106,12 @@ class TrackingFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- val feedbackButton = view.findViewById(R.id.tracking_feedback)
- val playSoundCard = view.findViewById(R.id.tracking_play_sound)
- val trackingDetailButton = view.findViewById(R.id.tracking_detail_scan)
+ val feedbackButton: CardView = view.findViewById(R.id.tracking_feedback)
+ val playSoundCard: CardView = view.findViewById(R.id.tracking_play_sound)
+ val trackingDetailButton: CardView = view.findViewById(R.id.tracking_detail_scan)
// TODO: include when finished
- // val observeTrackerButton = view.findViewById(R.id.tracking_observation)
- val map = view.findViewById(R.id.map)
+ // val observeTrackerButton: CardView = view.findViewById(R.id.tracking_observation)
+ val map: MapView = view.findViewById(R.id.map)
feedbackButton.setOnClickListener {
val directions: NavDirections =
@@ -190,7 +190,7 @@ class TrackingFragment : Fragment() {
addInteractions(view)
}
- fun addInteractions(view: View) {
+ private fun addInteractions(view: View) {
val button = view.findViewById(R.id.open_map_button)
diff --git a/app/src/main/res/layout/fragment_tracking.xml b/app/src/main/res/layout/fragment_tracking.xml
index 87c1f4ac..d2656377 100644
--- a/app/src/main/res/layout/fragment_tracking.xml
+++ b/app/src/main/res/layout/fragment_tracking.xml
@@ -71,8 +71,6 @@
-
-
+ app:layout_constraintTop_toBottomOf="@id/tracking_tiles" />
\ No newline at end of file
From 5cca04d4054667f86988f7ef199d44b04a5dfc0f Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 27 Sep 2023 11:21:25 +0200
Subject: [PATCH 003/154] make ObserveTracker functionality visible again
---
.../ui/tracking/TrackingFragment.kt | 16 ++++------
app/src/main/res/layout/fragment_tracking.xml | 32 +++++++++----------
2 files changed, 23 insertions(+), 25 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index fe063e7b..83332cba 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -109,8 +109,7 @@ class TrackingFragment : Fragment() {
val feedbackButton: CardView = view.findViewById(R.id.tracking_feedback)
val playSoundCard: CardView = view.findViewById(R.id.tracking_play_sound)
val trackingDetailButton: CardView = view.findViewById(R.id.tracking_detail_scan)
- // TODO: include when finished
- // val observeTrackerButton: CardView = view.findViewById(R.id.tracking_observation)
+ val observeTrackerButton: CardView = view.findViewById(R.id.tracking_observation)
val map: MapView = view.findViewById(R.id.map)
feedbackButton.setOnClickListener {
@@ -126,13 +125,12 @@ class TrackingFragment : Fragment() {
findNavController().navigate(directions)
}
- // TODO: include when finished
-// observeTrackerButton.setOnClickListener {
-// val deviceAddress: String = trackingViewModel.deviceAddress.value ?: return@setOnClickListener
-// val directions: NavDirections =
-// TrackingFragmentDirections.actionTrackingToObserveTracker(deviceAddress)
-// findNavController().navigate(directions)
-// }
+ observeTrackerButton.setOnClickListener {
+ val deviceAddress: String = trackingViewModel.deviceAddress.value ?: return@setOnClickListener
+ val directions: NavDirections =
+ TrackingFragmentDirections.actionTrackingToObserveTracker(deviceAddress)
+ findNavController().navigate(directions)
+ }
playSoundCard.setOnClickListener {
if (!Utility.checkAndRequestPermission(android.Manifest.permission.BLUETOOTH_CONNECT)) {
diff --git a/app/src/main/res/layout/fragment_tracking.xml b/app/src/main/res/layout/fragment_tracking.xml
index d2656377..40ff5600 100644
--- a/app/src/main/res/layout/fragment_tracking.xml
+++ b/app/src/main/res/layout/fragment_tracking.xml
@@ -223,22 +223,22 @@
bind:title="@{@string/tracking_detail_scan_title}"
bind:vm="@{vm}" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
From 76b2c50391e31fdc2007f797bde4c04b20078d21 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 27 Sep 2023 11:32:10 +0200
Subject: [PATCH 004/154] improve Observation Screen Design
---
.../main/res/layout/fragment_observe_tracker.xml | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/app/src/main/res/layout/fragment_observe_tracker.xml b/app/src/main/res/layout/fragment_observe_tracker.xml
index bf7421b5..3a0c4efe 100644
--- a/app/src/main/res/layout/fragment_observe_tracker.xml
+++ b/app/src/main/res/layout/fragment_observe_tracker.xml
@@ -13,10 +13,12 @@
+
+
+
+
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ bind:layout_constraintBottom_toTopOf="@+id/start_observation_button" />
+
From 79ad24e62d482ae65a4e1d7890b720d0f4c03d1d Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 2 Oct 2023 12:28:01 +0200
Subject: [PATCH 005/154] ObserveTrackerWorker now calls ScanBluetoothWorker
manually before checking Observation criteria. Clarify explanation how
Observe Tracker works.
---
app/src/main/AndroidManifest.xml | 2 +-
.../ui/tracking/ObserveTrackerFragment.kt | 6 ++++++
...rkersWorker.kt => ObserveTrackerWorker.kt} | 14 +++++++++++---
.../worker/ScheduleWorkersReceiver.kt | 4 ++--
.../res/layout/fragment_observe_tracker.xml | 19 ++++++++++++++++++-
app/src/main/res/values-de/strings.xml | 1 +
app/src/main/res/values/strings.xml | 1 +
7 files changed, 40 insertions(+), 7 deletions(-)
rename app/src/main/java/de/seemoo/at_tracking_detection/worker/{ScheduleWorkersWorker.kt => ObserveTrackerWorker.kt} (79%)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0ea01a81..003505f4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -77,7 +77,7 @@
-
+
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerFragment.kt
index d3d96a1b..65ecad9e 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerFragment.kt
@@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
+import android.widget.TextView
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
@@ -36,6 +37,11 @@ class ObserveTrackerFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+// if (deviceAddress != null) {
+// val text = view.findViewById(R.id.changing_id_text)
+// text.visibility = View.VISIBLE
+// }
+
val observationButton = view.findViewById
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/include_article_card.xml b/app/src/main/res/layout/include_article_card.xml
new file mode 100644
index 00000000..672e8775
--- /dev/null
+++ b/app/src/main/res/layout/include_article_card.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml
index 0429b71e..c516f8cf 100644
--- a/app/src/main/res/navigation/main_navigation.xml
+++ b/app/src/main/res/navigation/main_navigation.xml
@@ -49,7 +49,15 @@
android:name="de.seemoo.at_tracking_detection.ui.dashboard.ArticleFragment"
android:label="@string/title_article"
tools:layout="@layout/fragment_article">
-
+
+
+
From 333154eaba5c83ceb074911e726ccaee4554d68a Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 26 Oct 2023 22:21:54 +0200
Subject: [PATCH 018/154] add additional information in JSON
---
.../ui/dashboard/Article.kt | 109 ++++++++++++------
.../ui/dashboard/ArticleFragment.kt | 6 +-
.../ui/dashboard/DashboardRiskFragment.kt | 15 ++-
.../main/res/layout/include_article_card.xml | 1 +
.../main/res/navigation/main_navigation.xml | 3 +
app/src/main/res/values-de/strings.xml | 1 +
app/src/main/res/values/strings.xml | 2 +
7 files changed, 95 insertions(+), 42 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
index 8186e55f..5581b165 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
@@ -9,6 +9,9 @@ import java.net.URL
data class Article(
val title: String,
val author: String,
+ val readingTime: Int,
+ val previewText: String,
+ val cardColor: String,
val filename: String
)
@@ -31,38 +34,78 @@ fun getURL(filename: String): URL {
//}
fun downloadJson(url: String): String {
- return try {
- val connection = URL(url).openConnection()
- connection.setRequestProperty("Accept", "application/json")
- connection.connectTimeout = 1000
+ return """
+ {
+ "article1": {
+ "title": "Airguard Article Test 1",
+ "author": "Alexander Heinrich",
+ "readingTime": 5,
+ "previewText": "Preview Text 1",
+ "cardColor": "blue",
+ "filename": "test.md"
+ },
+ "article2": {
+ "title": "Airguard Article Test 2",
+ "author": "Dennis Arndt",
+ "readingTime": 2,
+ "previewText": "Preview Text 2",
+ "cardColor": "grey",
+ "filename": "test.md"
+ },
+ "article3": {
+ "title": "Airguard Article Test 3",
+ "author": "Leon Böttger",
+ "readingTime": 4,
+ "previewText": "Preview Text 3",
+ "cardColor": "grey",
+ "filename": "test.md"
+ }
+ }
+ """
- // Add a debug statement to print the URL of the connection
- println("Connection URL: ${connection.url}")
- val inputStream = connection.getInputStream()
- val reader = BufferedReader(InputStreamReader(inputStream))
- val json = reader.readLines().joinToString("\n")
- reader.close()
- inputStream.close()
- json
- } catch (e: Exception) {
- println("An error occurred while downloading the JSON file: ${e.message}")
- // TODO: "{}" // Return an empty JSON file if something goes wrong
- "{\n" +
- "\t\"article0\": {\n" +
- "\t\t\"title\": \"Airguard Article Test 1\",\n" +
- "\t\t\"author\": \"Alexander Heinrich\",\n" +
- "\t\t\"filename\": \"test.md\"\n" +
- "\t},\n" +
- "\t\"article1\": {\n" +
- "\t\t\"title\": \"Airguard Article Test 2\",\n" +
- "\t\t\"author\": \"Dennis Arndt\",\n" +
- "\t\t\"filename\": \"test.md\"\n" +
- "\t},\n" +
- "\t\"article2\": {\n" +
- "\t\t\"title\": \"Airguard Article Test 3\",\n" +
- "\t\t\"author\": \"Leon Böttger\",\n" +
- "\t\t\"filename\": \"test.md\"\n" +
- "\t}\n" +
- "}"
- }
+// return try {
+// val connection = URL(url).openConnection()
+// connection.setRequestProperty("Accept", "application/json")
+// connection.connectTimeout = 1000
+//
+// // Add a debug statement to print the URL of the connection
+// println("Connection URL: ${connection.url}")
+// val inputStream = connection.getInputStream()
+// val reader = BufferedReader(InputStreamReader(inputStream))
+// val json = reader.readLines().joinToString("\n")
+// reader.close()
+// inputStream.close()
+// json
+// } catch (e: Exception) {
+// println("An error occurred while downloading the JSON file: ${e.message}")
+// // TODO: "{}" // Return an empty JSON file if something goes wrong
+// """
+// {
+// {
+// "title": "Airguard Article Test 1",
+// "author": "Alexander Heinrich",
+// "readingTime": 5,
+// "previewText": "Preview Text 1",
+// "cardColor": "blue",
+// "filename": "test.md"
+// },
+// {
+// "title": "Airguard Article Test 2",
+// "author": "Dennis Arndt",
+// "readingTime": 2,
+// "previewText": "Preview Text 2",
+// "cardColor": "grey",
+// "filename": "test.md"
+// },
+// {
+// "title": "Airguard Article Test 3",
+// "author": "Leon Böttger",
+// "readingTime": 4,
+// "previewText": "Preview Text 3",
+// "cardColor": "grey",
+// "filename": "test.md"
+// }
+// }
+// """
+// }
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
index 93ecb36c..65a69f6b 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
@@ -41,14 +41,14 @@ class ArticleFragment : Fragment() {
val title = arguments?.getString("title")
val author = arguments?.getString("author")
+ val readingTime = arguments?.getInt("readingTime")
val filename = arguments?.getString("filename")
- // val url = getURL(filename!!)
- val url = getURL("test.md")
+ val url = getURL(filename!!)
titleTextView.text = title
authorTextView.text = author
- // articleReadingTimeView.text = calculateReadingTime(url).toString() + " min" // TODO strings
+ articleReadingTimeView.text = context?.getString(R.string.article_reading_time, readingTime)
markdownView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
index 740f6fb4..af583945 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
@@ -1,21 +1,18 @@
package de.seemoo.at_tracking_detection.ui.dashboard
+import android.graphics.Color
import android.os.Bundle
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
-import androidx.cardview.widget.CardView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.NavDirections
import androidx.navigation.fragment.findNavController
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.card.MaterialCardView
import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.R
@@ -86,9 +83,14 @@ class DashboardRiskFragment : Fragment() {
val layout = LayoutInflater.from(context).inflate(R.layout.include_article_card, null)
val textViewTitle = layout.findViewById(R.id.card_title)
val textViewPreviewText = layout.findViewById(R.id.card_text_preview)
+ val materialCard = layout.findViewById(R.id.material_card)
textViewTitle.text = article.title
- textViewPreviewText.text = article.title
+ textViewPreviewText.text = article.previewText
+
+ // TODO: for some reason not picking correct color
+ val colorResourceId = resources.getIdentifier(article.cardColor, "color", context?.packageName)
+ materialCard.setBackgroundColor(colorResourceId)
articleCard.addView(layout)
Timber.tag("CardAdded").d("Article card added: %s", article.title)
@@ -103,7 +105,8 @@ class DashboardRiskFragment : Fragment() {
DashboardRiskFragmentDirections.actionNavigationDashboardToArticleFragment(
author = article.author,
title = article.title,
- filename = article.filename
+ filename = article.filename,
+ readingTime = article.readingTime
)
findNavController().navigate(directions)
}
diff --git a/app/src/main/res/layout/include_article_card.xml b/app/src/main/res/layout/include_article_card.xml
index 672e8775..e2784ee2 100644
--- a/app/src/main/res/layout/include_article_card.xml
+++ b/app/src/main/res/layout/include_article_card.xml
@@ -3,6 +3,7 @@
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 11bcb017..10e77011 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -305,6 +305,7 @@
Datenlöschung erfolgreich
Etwas ist schief gelaufen
Keine Daten zum Löschen vorhanden
+ Lesedauer: %d min
Copyright
Entwickler: %s
Maintainer: %s
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f35fc008..66194d59 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -342,6 +342,8 @@
Something went wrong
No data to delete
+ Reading time: %d min
+
Copyright
Developer: %s
Maintainer: %s
From a1127e846ff2fa159b6faa27591e731c96c9999e Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Fri, 27 Oct 2023 16:19:30 +0200
Subject: [PATCH 019/154] Article Download now Coroutine, loading animation
while downloading JSON, remove old code
---
.../ui/dashboard/Article.kt | 99 ++++---------------
.../ui/dashboard/ArticleFragment.kt | 12 ---
.../ui/dashboard/DashboardRiskFragment.kt | 98 +++++++++---------
.../res/layout/fragment_dashboard_risk.xml | 6 ++
4 files changed, 75 insertions(+), 140 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
index 5581b165..441fb235 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
@@ -4,6 +4,7 @@ import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.BufferedReader
import java.io.InputStreamReader
+import java.net.HttpURLConnection
import java.net.URL
data class Article(
@@ -26,86 +27,26 @@ fun getURL(filename: String): URL {
return URL("https://tpe.seemoo.tu-darmstadt.de/static/articles/$filename")
}
-//fun calculateReadingTime(text: String): Int {
-// val wordsPerMinute = 250 // Average words per minute reading speed
-// val wordCount = text.split(" ").count()
-// val readingTimeInMinutes = wordCount / wordsPerMinute
-// return readingTimeInMinutes * 60 * 1000 // Convert to milliseconds
-//}
-
fun downloadJson(url: String): String {
- return """
- {
- "article1": {
- "title": "Airguard Article Test 1",
- "author": "Alexander Heinrich",
- "readingTime": 5,
- "previewText": "Preview Text 1",
- "cardColor": "blue",
- "filename": "test.md"
- },
- "article2": {
- "title": "Airguard Article Test 2",
- "author": "Dennis Arndt",
- "readingTime": 2,
- "previewText": "Preview Text 2",
- "cardColor": "grey",
- "filename": "test.md"
- },
- "article3": {
- "title": "Airguard Article Test 3",
- "author": "Leon Böttger",
- "readingTime": 4,
- "previewText": "Preview Text 3",
- "cardColor": "grey",
- "filename": "test.md"
+ val connection = URL(url).openConnection() as HttpURLConnection
+
+ return try {
+ connection.requestMethod = "GET"
+ val responseCode = connection.responseCode
+
+ if (responseCode == HttpURLConnection.HTTP_OK) {
+ val reader = BufferedReader(InputStreamReader(connection.inputStream))
+ val response = StringBuilder()
+ var inputLine: String?
+ while (reader.readLine().also { inputLine = it } != null) {
+ response.append(inputLine)
}
+ reader.close()
+ response.toString()
+ } else {
+ "{}"
}
- """
-
-// return try {
-// val connection = URL(url).openConnection()
-// connection.setRequestProperty("Accept", "application/json")
-// connection.connectTimeout = 1000
-//
-// // Add a debug statement to print the URL of the connection
-// println("Connection URL: ${connection.url}")
-// val inputStream = connection.getInputStream()
-// val reader = BufferedReader(InputStreamReader(inputStream))
-// val json = reader.readLines().joinToString("\n")
-// reader.close()
-// inputStream.close()
-// json
-// } catch (e: Exception) {
-// println("An error occurred while downloading the JSON file: ${e.message}")
-// // TODO: "{}" // Return an empty JSON file if something goes wrong
-// """
-// {
-// {
-// "title": "Airguard Article Test 1",
-// "author": "Alexander Heinrich",
-// "readingTime": 5,
-// "previewText": "Preview Text 1",
-// "cardColor": "blue",
-// "filename": "test.md"
-// },
-// {
-// "title": "Airguard Article Test 2",
-// "author": "Dennis Arndt",
-// "readingTime": 2,
-// "previewText": "Preview Text 2",
-// "cardColor": "grey",
-// "filename": "test.md"
-// },
-// {
-// "title": "Airguard Article Test 3",
-// "author": "Leon Böttger",
-// "readingTime": 4,
-// "previewText": "Preview Text 3",
-// "cardColor": "grey",
-// "filename": "test.md"
-// }
-// }
-// """
-// }
+ } finally {
+ connection.disconnect()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
index 65a69f6b..05484df0 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
@@ -6,27 +6,15 @@ import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import com.mukesh.MarkDown
import de.seemoo.at_tracking_detection.R
-import org.w3c.dom.Text
-import java.net.URL
class ArticleFragment : Fragment() {
-
-// private val titleTextView: TextView by lazy { view?.findViewById(R.id.article_title) as TextView }
-// private val authorTextView: TextView by lazy { view?.findViewById(R.id.article_author) as TextView }
-// private val readingTimeTextView: TextView by lazy { view?.findViewById(R.id.article_reading_time) as TextView }
-
- // private val articleWebView: WebView by lazy { view?.findViewById(R.id.article_webview) as WebView }
-
-// private val composeView: ComposeView by lazy { view?.findViewById(R.id.markdown) as ComposeView }
-
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
index af583945..9de8f994 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
@@ -1,22 +1,26 @@
package de.seemoo.at_tracking_detection.ui.dashboard
-import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
+import android.widget.ProgressBar
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDirections
import androidx.navigation.fragment.findNavController
import com.google.android.material.card.MaterialCardView
import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.databinding.FragmentDashboardRiskBinding
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import timber.log.Timber
@@ -55,67 +59,63 @@ class DashboardRiskFragment : Fragment() {
findNavController().navigate(directions)
}
-// val articleCard: MaterialCardView = view.findViewById(R.id.article_card)
-// articleCard.setOnClickListener {
-// val directions: NavDirections =
-// DashboardRiskFragmentDirections.actionNavigationDashboardToArticleFragment(author = "test", title = "test", filename = "test.md")
-// findNavController().navigate(directions)
-// }
-
- val articlesJSON = downloadJson("https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json")
-
- Timber.d("Articles JSON: %s", articlesJSON)
-
- val articles = parseArticles(articlesJSON)
-
val articlesContainer = view.findViewById(R.id.articles_container)
+ val progressBar = view.findViewById(R.id.loading_progress_bar)
- Timber.d("Number of Articles: %s", articles.size)
+ lifecycleScope.launch(Dispatchers.IO) {
+ progressBar.visibility = View.VISIBLE
+ val articlesJSON = downloadJson("https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json")
+ Timber.d("Articles JSON: %s", articlesJSON)
- // Create a new LinearLayout to hold the ArticleCards
- val articleCardsLinearLayout = LinearLayout(context)
- articleCardsLinearLayout.orientation = LinearLayout.VERTICAL
- articleCardsLinearLayout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ withContext(Dispatchers.Main) {
+ val articles = parseArticles(articlesJSON)
+ Timber.d("Number of Articles: %s", articles.size)
- for (article in articles) {
- val articleCard = MaterialCardView(context)
+ // Create a new LinearLayout to hold the ArticleCards
+ val articleCardsLinearLayout = LinearLayout(context)
+ articleCardsLinearLayout.orientation = LinearLayout.VERTICAL
+ articleCardsLinearLayout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
- val layout = LayoutInflater.from(context).inflate(R.layout.include_article_card, null)
- val textViewTitle = layout.findViewById(R.id.card_title)
- val textViewPreviewText = layout.findViewById(R.id.card_text_preview)
- val materialCard = layout.findViewById(R.id.material_card)
+ for (article in articles) {
+ val articleCard = MaterialCardView(context)
- textViewTitle.text = article.title
- textViewPreviewText.text = article.previewText
+ val layout = LayoutInflater.from(context).inflate(R.layout.include_article_card, null)
+ val textViewTitle = layout.findViewById(R.id.card_title)
+ val textViewPreviewText = layout.findViewById(R.id.card_text_preview)
+ val materialCard = layout.findViewById(R.id.material_card)
- // TODO: for some reason not picking correct color
- val colorResourceId = resources.getIdentifier(article.cardColor, "color", context?.packageName)
- materialCard.setBackgroundColor(colorResourceId)
+ textViewTitle.text = article.title
+ textViewPreviewText.text = article.previewText
- articleCard.addView(layout)
- Timber.tag("CardAdded").d("Article card added: %s", article.title)
+ val colorResourceId = resources.getIdentifier(article.cardColor, "color", context?.packageName)
+ materialCard.setBackgroundColor(colorResourceId)
- articleCard.layoutParams = LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT,
- LinearLayout.LayoutParams.WRAP_CONTENT
- )
+ articleCard.addView(layout)
+ Timber.tag("CardAdded").d("Article card added: %s", article.title)
- articleCard.setOnClickListener {
- val directions: NavDirections =
- DashboardRiskFragmentDirections.actionNavigationDashboardToArticleFragment(
- author = article.author,
- title = article.title,
- filename = article.filename,
- readingTime = article.readingTime
+ articleCard.layoutParams = LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
)
- findNavController().navigate(directions)
- }
- articleCardsLinearLayout.addView(articleCard)
+ articleCard.setOnClickListener {
+ val directions: NavDirections =
+ DashboardRiskFragmentDirections.actionNavigationDashboardToArticleFragment(
+ author = article.author,
+ title = article.title,
+ filename = article.filename,
+ readingTime = article.readingTime
+ )
+ findNavController().navigate(directions)
+ }
+
+ articleCardsLinearLayout.addView(articleCard)
+ }
+
+ articlesContainer.addView(articleCardsLinearLayout)
+ progressBar.visibility = View.GONE
+ }
}
-
- articlesContainer.addView(articleCardsLinearLayout)
-
}
override fun onStart() {
diff --git a/app/src/main/res/layout/fragment_dashboard_risk.xml b/app/src/main/res/layout/fragment_dashboard_risk.xml
index 0bcc5b0f..731821db 100644
--- a/app/src/main/res/layout/fragment_dashboard_risk.xml
+++ b/app/src/main/res/layout/fragment_dashboard_risk.xml
@@ -39,6 +39,12 @@
android:layout_height="wrap_content"
bind:vm="@{vm}" />
+
+
Date: Fri, 27 Oct 2023 16:35:46 +0200
Subject: [PATCH 020/154] Design improvement
---
.../at_tracking_detection/ui/dashboard/ArticleFragment.kt | 8 ++++++--
.../ui/dashboard/DashboardRiskFragment.kt | 8 ++++++--
2 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
index 05484df0..e4477d56 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
@@ -37,16 +37,20 @@ class ArticleFragment : Fragment() {
titleTextView.text = title
authorTextView.text = author
articleReadingTimeView.text = context?.getString(R.string.article_reading_time, readingTime)
+
+ var modifier = Modifier.fillMaxSize()
+
markdownView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
MarkDown(
url = url,
- modifier = Modifier.fillMaxSize()
+ modifier = modifier
)
}
}
return view
}
-}
\ No newline at end of file
+}
+
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
index 9de8f994..73f7fe48 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
@@ -1,5 +1,6 @@
package de.seemoo.at_tracking_detection.ui.dashboard
+import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -49,6 +50,7 @@ class DashboardRiskFragment : Fragment() {
}
+ @SuppressLint("DiscouragedApi")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -95,8 +97,10 @@ class DashboardRiskFragment : Fragment() {
articleCard.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
- LinearLayout.LayoutParams.WRAP_CONTENT
- )
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ ).apply {
+ topMargin = 22
+ }
articleCard.setOnClickListener {
val directions: NavDirections =
From d172e47926b55eaf1ce1e9fee816366b435d53bb Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 28 Oct 2023 15:37:46 +0200
Subject: [PATCH 021/154] Article offline handling
---
.../ui/dashboard/Article.kt | 20 ++++++++++-
.../ui/dashboard/ArticleFragment.kt | 36 +++++++++++++++----
.../ui/dashboard/DashboardRiskFragment.kt | 20 ++++++-----
3 files changed, 59 insertions(+), 17 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
index 441fb235..bf53ae95 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
@@ -2,6 +2,7 @@ package de.seemoo.at_tracking_detection.ui.dashboard
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
+import timber.log.Timber
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
@@ -28,8 +29,22 @@ fun getURL(filename: String): URL {
}
fun downloadJson(url: String): String {
+ val errorReturnValue = """
+ {
+ "article0": {
+ "title": "No internet connection",
+ "author": "Dennis Arndt",
+ "readingTime": 0,
+ "previewText": "You are currently not connected to the internet. You will find articles that will help you navigate the app here when you are connected to the internet.",
+ "cardColor": "blue_card_background",
+ "filename": ""
+ }
+ }
+ """.trimIndent()
+
val connection = URL(url).openConnection() as HttpURLConnection
+
return try {
connection.requestMethod = "GET"
val responseCode = connection.responseCode
@@ -44,8 +59,11 @@ fun downloadJson(url: String): String {
reader.close()
response.toString()
} else {
- "{}"
+ errorReturnValue
}
+ } catch (e: Exception) {
+ Timber.e(e)
+ errorReturnValue
} finally {
connection.disconnect()
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
index e4477d56..9a2e00fc 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
@@ -1,10 +1,14 @@
package de.seemoo.at_tracking_detection.ui.dashboard
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
+import android.widget.Toast
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
@@ -12,6 +16,7 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import com.mukesh.MarkDown
import de.seemoo.at_tracking_detection.R
+import timber.log.Timber
class ArticleFragment : Fragment() {
@@ -40,14 +45,31 @@ class ArticleFragment : Fragment() {
var modifier = Modifier.fillMaxSize()
- markdownView.apply {
- setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
- setContent {
- MarkDown(
- url = url,
- modifier = modifier
- )
+ val connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
+
+ fun errorHandling() {
+ Toast.makeText(requireContext(), "No internet connection. Cannot load article.", Toast.LENGTH_SHORT).show()
+ }
+
+ if ((networkCapabilities != null) && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ try {
+ markdownView.apply {
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ setContent {
+ MarkDown(
+ url = url,
+ modifier = modifier
+ )
+ }
+ }
+ } catch (e: Exception) {
+ Timber.d(e)
+ errorHandling()
}
+
+ } else {
+ errorHandling()
}
return view
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
index 73f7fe48..efdebb10 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
@@ -102,15 +102,17 @@ class DashboardRiskFragment : Fragment() {
topMargin = 22
}
- articleCard.setOnClickListener {
- val directions: NavDirections =
- DashboardRiskFragmentDirections.actionNavigationDashboardToArticleFragment(
- author = article.author,
- title = article.title,
- filename = article.filename,
- readingTime = article.readingTime
- )
- findNavController().navigate(directions)
+ if(article.filename != "") {
+ articleCard.setOnClickListener {
+ val directions: NavDirections =
+ DashboardRiskFragmentDirections.actionNavigationDashboardToArticleFragment(
+ author = article.author,
+ title = article.title,
+ filename = article.filename,
+ readingTime = article.readingTime
+ )
+ findNavController().navigate(directions)
+ }
}
articleCardsLinearLayout.addView(articleCard)
From ed0dce197294e35eab53f03f8a2f38887e417c2f Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 4 Nov 2023 14:23:10 +0100
Subject: [PATCH 022/154] support Online Articles in multiple languages
---
.../ui/dashboard/Article.kt | 29 ++++++++++++++++---
.../ui/dashboard/DashboardRiskFragment.kt | 4 ++-
2 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
index bf53ae95..8047e9ef 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
@@ -28,9 +28,29 @@ fun getURL(filename: String): URL {
return URL("https://tpe.seemoo.tu-darmstadt.de/static/articles/$filename")
}
-fun downloadJson(url: String): String {
- val errorReturnValue = """
- {
+fun downloadJson(language: String): String {
+ val fallbackURL = "https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json"
+
+ // TODO: set different language article urls
+ val url = when (language) {
+ "de" -> "https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json"
+ "en" -> "https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json"
+ else -> fallbackURL
+ }
+
+ val errorReturnValue = when (language) {
+ "de" -> """{
+ "article0": {
+ "title": "Keine Internetverbindung",
+ "author": "Dennis Arndt",
+ "readingTime": 0,
+ "previewText": "Es besteht aktuell keine Internetverbindung. Hier werden Artikel angezeigt, die dir helfen die App zu bedienen, sobald du wieder mit dem Internet verbunden bist.",
+ "cardColor": "blue_card_background",
+ "filename": ""
+ }
+ }
+ """.trimIndent()
+ else -> """{
"article0": {
"title": "No internet connection",
"author": "Dennis Arndt",
@@ -40,7 +60,8 @@ fun downloadJson(url: String): String {
"filename": ""
}
}
- """.trimIndent()
+ """.trimIndent()
+ }
val connection = URL(url).openConnection() as HttpURLConnection
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
index efdebb10..e085f593 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
@@ -66,7 +66,9 @@ class DashboardRiskFragment : Fragment() {
lifecycleScope.launch(Dispatchers.IO) {
progressBar.visibility = View.VISIBLE
- val articlesJSON = downloadJson("https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json")
+
+ val locale = resources.configuration.locales[0].language
+ val articlesJSON = downloadJson(locale)
Timber.d("Articles JSON: %s", articlesJSON)
withContext(Dispatchers.Main) {
From 42a90dd7fd0f06954f33ab61b784b36a2beb4b57 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 11 Nov 2023 11:54:29 +0100
Subject: [PATCH 023/154] upgrade to gradle 8.1.3
---
build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/build.gradle b/build.gradle
index 78a52f27..774dd8f5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,7 +13,7 @@ buildscript {
}
}
dependencies {
- classpath "com.android.tools.build:gradle:8.1.2"
+ classpath 'com.android.tools.build:gradle:8.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0"
From 53216024fe37094e27e0c40dd0efcba1c28ab00b Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 11 Nov 2023 12:19:39 +0100
Subject: [PATCH 024/154] fix security issues in the AndroidManifest
---
app/src/main/AndroidManifest.xml | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 003505f4..5a8ad88e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,7 +29,6 @@
-
+ android:parentActivityName=".ui.MainActivity"
+ android:exported="false" />
-
+
-
+
-
+
+ android:enabled="true"
+ android:exported="false"/>
Date: Sat, 11 Nov 2023 12:29:22 +0100
Subject: [PATCH 025/154] update backup rules to confirm to android 12
standards
---
app/src/main/AndroidManifest.xml | 5 +++--
app/src/main/res/xml/data_extraction_rules.xml | 7 +++++++
2 files changed, 10 insertions(+), 2 deletions(-)
create mode 100644 app/src/main/res/xml/data_extraction_rules.xml
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5a8ad88e..370e21bf 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -31,14 +31,15 @@
+ tools:targetApi="tiramisu"
+ android:dataExtractionRules="@xml/data_extraction_rules"
+ android:fullBackupContent="false">
+
+
+
+
+
+
\ No newline at end of file
From de32b38df47e767813b51da8d72f8b7691ec1436 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 11 Nov 2023 13:02:02 +0100
Subject: [PATCH 026/154] optimize Article Offline Handling and add old cards
again
---
.../ui/dashboard/Article.kt | 50 ++++++++++---------
.../ui/dashboard/DashboardRiskFragment.kt | 3 +-
app/src/main/res/values-de/strings.xml | 6 +++
app/src/main/res/values/strings.xml | 3 ++
4 files changed, 37 insertions(+), 25 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
index 8047e9ef..9ce05d98 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
@@ -2,6 +2,8 @@ package de.seemoo.at_tracking_detection.ui.dashboard
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
+import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
+import de.seemoo.at_tracking_detection.R
import timber.log.Timber
import java.io.BufferedReader
import java.io.InputStreamReader
@@ -28,44 +30,46 @@ fun getURL(filename: String): URL {
return URL("https://tpe.seemoo.tu-darmstadt.de/static/articles/$filename")
}
-fun downloadJson(language: String): String {
- val fallbackURL = "https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json"
+fun downloadJson(): String {
+ val url = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.article_download_url)
- // TODO: set different language article urls
- val url = when (language) {
- "de" -> "https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json"
- "en" -> "https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json"
- else -> fallbackURL
- }
+ val articleOfflineTitle = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.article_offline_header)
+ val articleOfflineText = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.article_offline_text)
+ val iveGotANotification = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.i_got_a_notification_what_should_i_do)
+ val searchManually = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.notification_help)
+ val iCanNotFindTracker = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.i_cannot_find_the_tracker)
+ val findTackerHelp = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.find_tracker_help)
- val errorReturnValue = when (language) {
- "de" -> """{
+ val errorReturnValue = """{
"article0": {
- "title": "Keine Internetverbindung",
+ "title": "$articleOfflineTitle",
"author": "Dennis Arndt",
"readingTime": 0,
- "previewText": "Es besteht aktuell keine Internetverbindung. Hier werden Artikel angezeigt, die dir helfen die App zu bedienen, sobald du wieder mit dem Internet verbunden bist.",
+ "previewText": "$articleOfflineText",
"cardColor": "blue_card_background",
"filename": ""
- }
- }
- """.trimIndent()
- else -> """{
- "article0": {
- "title": "No internet connection",
- "author": "Dennis Arndt",
+ },
+ "article1": {
+ "title": "$iveGotANotification",
+ "author": "Alexander Heinrich",
"readingTime": 0,
- "previewText": "You are currently not connected to the internet. You will find articles that will help you navigate the app here when you are connected to the internet.",
- "cardColor": "blue_card_background",
+ "previewText": "$searchManually",
+ "cardColor": "gray_card_background",
+ "filename": ""
+ },
+ "article2": {
+ "title": "$iCanNotFindTracker",
+ "author": "Alexander Heinrich",
+ "readingTime": 0,
+ "previewText": "$findTackerHelp",
+ "cardColor": "gray_card_background",
"filename": ""
}
}
""".trimIndent()
- }
val connection = URL(url).openConnection() as HttpURLConnection
-
return try {
connection.requestMethod = "GET"
val responseCode = connection.responseCode
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
index e085f593..807fda55 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
@@ -67,8 +67,7 @@ class DashboardRiskFragment : Fragment() {
lifecycleScope.launch(Dispatchers.IO) {
progressBar.visibility = View.VISIBLE
- val locale = resources.configuration.locales[0].language
- val articlesJSON = downloadJson(locale)
+ val articlesJSON = downloadJson()
Timber.d("Articles JSON: %s", articlesJSON)
withContext(Dispatchers.Main) {
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 10e77011..d1eef9c2 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -305,8 +305,14 @@
Datenlöschung erfolgreich
Etwas ist schief gelaufen
Keine Daten zum Löschen vorhanden
+
+ https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json
Lesedauer: %d min
+ Keine Internetverbindung
+ Es besteht aktuell keine Internetverbindung. Hier werden Artikel angezeigt, die dir helfen die App zu bedienen, sobald du wieder mit dem Internet verbunden bist.
+
Copyright
Entwickler: %s
Maintainer: %s
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 66194d59..fffd391b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -342,7 +342,10 @@
Something went wrong
No data to delete
+ https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json
Reading time: %d min
+ No internet connection
+ You are currently not connected to the internet. You will find articles that will help you navigate the app here when you are connected to the internet.
Copyright
Developer: %s
From fa2c4eade36bc0fc03ccf190049a510358a4112e Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 19 Nov 2023 16:22:03 +0100
Subject: [PATCH 027/154] upgrade gradle and some libraries
---
app/build.gradle | 4 ++--
build.gradle | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 894ad3f7..e823f0c2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -103,7 +103,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.cardview:cardview:1.0.0'
- implementation 'androidx.recyclerview:recyclerview:1.3.1'
+ implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
@@ -146,7 +146,7 @@ dependencies {
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1'
- coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
//Finds memory leaks while running the app in Debug mode
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
diff --git a/build.gradle b/build.gradle
index 774dd8f5..5d05bb3a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,7 +13,7 @@ buildscript {
}
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.1.3'
+ classpath 'com.android.tools.build:gradle:8.1.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0"
From 78fcf618fe2abba81e65c78b4b29090320143c2d Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 19 Nov 2023 16:43:23 +0100
Subject: [PATCH 028/154] speed up map rendering and add permission necessary
for Android 14
---
app/src/main/AndroidManifest.xml | 1 +
.../de/seemoo/at_tracking_detection/util/Utility.kt | 11 ++++-------
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 370e21bf..505363cd 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -20,6 +20,7 @@
+
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 baaa4610..1afc3d90 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
@@ -103,7 +103,6 @@ object Utility {
val geoPointList = ArrayList()
val markerList = ArrayList()
-
map.setTileSource(TileSourceFactory.MAPNIK)
map.setUseDataConnection(true)
map.setMultiTouchControls(true)
@@ -113,10 +112,11 @@ object Utility {
withContext(Dispatchers.Default) {
locationList
.filter { it.locationId != 0 }
- .map { location ->
+ .forEach { location ->
if (!map.isShown) {
- return@map
+ return@forEach
}
+
val marker = Marker(map)
val geoPoint = GeoPoint(location.latitude, location.longitude)
marker.position = geoPoint
@@ -134,8 +134,8 @@ object Utility {
}
}
}
- map.overlays.addAll(markerList)
+ map.overlays.addAll(markerList)
Timber.d("Added ${geoPointList.size} markers to the map!")
if (connectWithPolyline) {
@@ -159,7 +159,6 @@ object Utility {
try {
Timber.d("Zoom in to bounds -> $boundingBox")
map.zoomToBoundingBox(boundingBox, true, 100, MAX_ZOOM_LEVEL, 1)
-
} catch (e: IllegalArgumentException) {
mapController.setCenter(boundingBox.centerWithDateLine)
mapController.setZoom(10.0)
@@ -167,8 +166,6 @@ object Utility {
}
}
- // map.zoomToBoundingBox(boundingBox, true)
-
return true
}
From 5aa2324ad76fae049f11c57d5c3a5b3b3d56c803 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 20 Nov 2023 15:47:02 +0100
Subject: [PATCH 029/154] Rename SmartTags to be consistent with Samsungs
current product lines
---
.../database/models/device/types/SmartTag.kt | 4 +---
.../database/models/device/types/SmartTagPlus.kt | 4 +---
app/src/main/res/values-de/strings.xml | 2 ++
app/src/main/res/values/strings.xml | 3 +++
4 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTag.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTag.kt
index 582a4589..0c137e0b 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTag.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTag.kt
@@ -2,10 +2,8 @@ package de.seemoo.at_tracking_detection.database.models.device.types
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
-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
@@ -45,7 +43,7 @@ class SmartTag(override val id: Int) : SamsungDevice(id) {
get() = DeviceType.GALAXY_SMART_TAG
override val defaultDeviceName: String
- get() = "SmartTag"
+ get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.smarttag_no_uwb)
override val statusByteDeviceType: UInt
get() = 0u
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTagPlus.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTagPlus.kt
index ba0d0a93..b3af1bf1 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTagPlus.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTagPlus.kt
@@ -2,10 +2,8 @@ package de.seemoo.at_tracking_detection.database.models.device.types
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
-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
@@ -45,7 +43,7 @@ class SmartTagPlus(override val id: Int) : SamsungDevice(id) {
get() = DeviceType.GALAXY_SMART_TAG_PLUS
override val defaultDeviceName: String
- get() = "SmartTag Plus"
+ get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.smarttag_uwb)
override val statusByteDeviceType: UInt
get() = 0u
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 6d8f522b..8557ad13 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -306,6 +306,8 @@
Etwas ist schief gelaufen
Keine Daten zum Löschen vorhanden
+ SmartTag (mit UWB)
+
https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json
Lesedauer: %d min
Keine Internetverbindung
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3b510ac4..556df528 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -336,6 +336,9 @@
You are already observing this tracker
Credits
+ SmartTag
+ SmartTag (with UWB)
+
Request deletion of Data
If you would like us to delete all the data we have collected from you as part of our study, please click the button below. This will also opt you out of further participation in the study. If you would like to continue supporting our research at any point, you can simply reactivate the onboarding process and click yes to study participation.
Data deletion successful
From 201b7e32c9b56517017be363b84e6bfaaf0800b4 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 20 Nov 2023 16:00:34 +0100
Subject: [PATCH 030/154] fix for common IllegalStateException in
PlaySoundDialogFragment
---
.../ui/scan/dialog/PlaySoundDialogFragment.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/PlaySoundDialogFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/PlaySoundDialogFragment.kt
index 88fd0e9e..bce24724 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/PlaySoundDialogFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/PlaySoundDialogFragment.kt
@@ -82,7 +82,7 @@ class PlaySoundDialogFragment constructor(scanResult: ScanResult) : BottomSheetD
}
private fun dismissWithDelay() {
- if (isAdded) {
+ if (isAdded && !isDetached && !isRemoving) {
Handler(Looper.getMainLooper()).postDelayed({
dismiss()
}, DIALOG_CLOSE_DELAY)
From bb3993b14201643c80ee98a012663a71dee83abf Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 22 Nov 2023 20:53:56 +0100
Subject: [PATCH 031/154] add previewImage to the Article Function
---
app/build.gradle | 2 ++
.../ui/dashboard/Article.kt | 5 ++++
.../ui/dashboard/DashboardRiskFragment.kt | 24 +++++++++++++++++--
.../main/res/layout/include_article_card.xml | 6 +++++
app/src/main/res/values-de/strings.xml | 2 ++
app/src/main/res/values/strings.xml | 2 ++
6 files changed, 39 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index e823f0c2..dd88b8ae 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -128,6 +128,8 @@ dependencies {
implementation 'com.github.mukeshsolanki:MarkdownView-Android:2.0.0'
+ implementation 'com.github.bumptech.glide:glide:4.16.0'
+
kapt "com.google.dagger:hilt-compiler:$hilt_version"
kapt 'androidx.hilt:hilt-compiler:1.0.0'
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
index 9ce05d98..9c88b4cd 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
@@ -16,6 +16,7 @@ data class Article(
val readingTime: Int,
val previewText: String,
val cardColor: String,
+ val preview_image: String, // TODO: Rename when in production to PreviewImage, also in JSON
val filename: String
)
@@ -30,6 +31,10 @@ fun getURL(filename: String): URL {
return URL("https://tpe.seemoo.tu-darmstadt.de/static/articles/$filename")
}
+fun getImageURL(filename: String): String {
+ return "https://tpe.seemoo.tu-darmstadt.de/articles/$filename"
+}
+
fun downloadJson(): String {
val url = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.article_download_url)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
index 807fda55..66ca3b53 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
@@ -5,6 +5,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
@@ -15,6 +16,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDirections
import androidx.navigation.fragment.findNavController
+import com.bumptech.glide.Glide
import com.google.android.material.card.MaterialCardView
import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.R
@@ -85,10 +87,15 @@ class DashboardRiskFragment : Fragment() {
val layout = LayoutInflater.from(context).inflate(R.layout.include_article_card, null)
val textViewTitle = layout.findViewById(R.id.card_title)
val textViewPreviewText = layout.findViewById(R.id.card_text_preview)
+ val imageViewPreview = layout.findViewById(R.id.preview_image)
val materialCard = layout.findViewById(R.id.material_card)
textViewTitle.text = article.title
- textViewPreviewText.text = article.previewText
+ if (article.previewText.isNotEmpty()){
+ textViewPreviewText.text = article.previewText
+ } else {
+ textViewPreviewText.visibility = View.GONE
+ }
val colorResourceId = resources.getIdentifier(article.cardColor, "color", context?.packageName)
materialCard.setBackgroundColor(colorResourceId)
@@ -103,7 +110,20 @@ class DashboardRiskFragment : Fragment() {
topMargin = 22
}
- if(article.filename != "") {
+ if (article.preview_image.isNotEmpty()) { // TODO: Rename when in production to PreviewImage, also in JSON
+ val imageURL = getImageURL(article.preview_image) // TODO: Rename when in production to PreviewImage, also in JSON
+ // TODO: Load image from URL
+ context?.let {
+ Glide.with(it)
+ .load(imageURL)
+ .fitCenter()
+ .into(imageViewPreview)
+ }
+ } else {
+ imageViewPreview.visibility = View.GONE
+ }
+
+ if (article.filename.isNotEmpty()) {
articleCard.setOnClickListener {
val directions: NavDirections =
DashboardRiskFragmentDirections.actionNavigationDashboardToArticleFragment(
diff --git a/app/src/main/res/layout/include_article_card.xml b/app/src/main/res/layout/include_article_card.xml
index e2784ee2..98d7d523 100644
--- a/app/src/main/res/layout/include_article_card.xml
+++ b/app/src/main/res/layout/include_article_card.xml
@@ -27,6 +27,12 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
+
+
Etwas ist schief gelaufen
Keine Daten zum Löschen vorhanden
+ Vorschaubild für diesen Artikel
+
SmartTag (mit UWB)
https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 556df528..d1822d8e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -323,6 +323,8 @@
Version %s
Contact Us
+ Preview Image for this article
+
Observe Tracker
Image of a clock
Start Observation
From 7f06d311b7cbd2823719b02be68fef5118eb1874 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Fri, 24 Nov 2023 15:47:13 +0100
Subject: [PATCH 032/154] Fix for Delete Study Data
---
.../ui/dashboard/DashboardRiskFragment.kt | 1 -
.../ui/onboarding/ShareDataFragment.kt | 9 ------
.../ui/settings/DataDeletionFragment.kt | 30 +++++++++++--------
.../ui/settings/SettingsFragment.kt | 8 +++++
app/src/main/res/values-de/strings.xml | 1 +
app/src/main/res/values/strings.xml | 1 +
6 files changed, 27 insertions(+), 23 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
index 66ca3b53..69cb167f 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
@@ -112,7 +112,6 @@ class DashboardRiskFragment : Fragment() {
if (article.preview_image.isNotEmpty()) { // TODO: Rename when in production to PreviewImage, also in JSON
val imageURL = getImageURL(article.preview_image) // TODO: Rename when in production to PreviewImage, also in JSON
- // TODO: Load image from URL
context?.let {
Glide.with(it)
.load(imageURL)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/ShareDataFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/ShareDataFragment.kt
index 0d8912e6..0a9af334 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/ShareDataFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/ShareDataFragment.kt
@@ -60,15 +60,6 @@ class ShareDataFragment : Fragment(), SlidePolicy {
it.setBackgroundColor(Color.GREEN)
noButton.setBackgroundColor(Color.TRANSPARENT)
sharedPreferences.edit().putBoolean("share_data", true).apply()
-
- var token = SharedPrefs.token
- if (token == null) {
- CoroutineScope(Dispatchers.Main).launch {
- val response = api.getToken().body() ?: return@launch
- token = response.token
- SharedPrefs.token = token
- }
- }
}
noButton.setOnClickListener {
buttonPressed = true
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 aeab063f..2bb608bf 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,26 +43,30 @@ class DataDeletionFragment : Fragment() {
deletionButton.setOnClickListener {
val token = SharedPrefs.token
- sharedPreferences.edit().putBoolean("share_data", false).apply()
-
CoroutineScope(Dispatchers.Main).launch {
if (!api.ping().isSuccessful) {
Timber.e("Server not available!")
- return@launch
- }
- if (token != null) {
- val response = api.deleteStudyData(token)
- val text = when {
- response.isSuccessful -> R.string.delete_data_success
- else -> R.string.delete_data_error
- }
+ val text = R.string.delete_data_server_error
+ Snackbar.make(view, text, Snackbar.LENGTH_LONG).show()
+ } 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()
+ } else {
+ val response = api.deleteStudyData(token)
+
if (response.isSuccessful) {
+ Timber.e("Data Deletion Successful!")
+ 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()
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()
}
- } else {
- val text = R.string.delete_data_no_data
- Snackbar.make(view, text, Snackbar.LENGTH_LONG).show()
}
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/SettingsFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/SettingsFragment.kt
index d2602127..eafca39c 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/SettingsFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/SettingsFragment.kt
@@ -34,6 +34,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
updatePermissionSettings()
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceListener)
+ if (SharedPrefs.token == null && !SharedPrefs.shareData) {
+ findPreference("delete_study_data")?.isVisible = false
+ }
+
findPreference("information_contact")?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
view?.findNavController()?.navigate(R.id.action_settings_to_information)
@@ -73,8 +77,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
if (SharedPrefs.shareData) {
Timber.d("Enabled background statistics sharing!")
backgroundWorkScheduler.scheduleShareData()
+ findPreference("delete_study_data")?.isVisible = true
} else {
backgroundWorkScheduler.removeShareData()
+ if (SharedPrefs.token == null) {
+ findPreference("delete_study_data")?.isVisible = false
+ }
}
}
"use_location" -> {
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index b3360d0f..127191a7 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -304,6 +304,7 @@
Wenn Sie möchten, dass wir alle Daten, die wir im Rahmen unserer Studie von Ihnen erhoben haben, löschen, klicken Sie bitte auf die Schaltfläche unten. Dadurch werden Sie auch von der weiteren Teilnahme an der Studie ausgeschlossen und es werden keine weiteren Daten von Ihnen erhoben. Wenn Sie unsere Forschung weiterhin unterstützen möchten, können Sie einfach den Onboarding-Dialog erneut durchlaufen und auf "Ja" bei der Studienteilnahme klicken.
Datenlöschung erfolgreich
Etwas ist schief gelaufen
+ Server nicht erreichbar. Versuche es später nochmal.
Keine Daten zum Löschen vorhanden
Vorschaubild für diesen Artikel
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d1822d8e..4a293121 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -345,6 +345,7 @@
If you would like us to delete all the data we have collected from you as part of our study, please click the button below. This will also opt you out of further participation in the study. If you would like to continue supporting our research at any point, you can simply reactivate the onboarding process and click yes to study participation.
Data deletion successful
Something went wrong
+ Server is not reachable. Try again later.
No data to delete
https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json
From 534872aeb67ead996a3c1916fbd39d24ae313288 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 25 Nov 2023 11:57:32 +0100
Subject: [PATCH 033/154] Relevant Tracking Days can be set inividually for
each Type of tracker, set for 2 Days for Chipolo and Tile
---
.../seemoo/at_tracking_detection/ScanDBTests.kt | 6 ++++--
.../database/models/device/DeviceContext.kt | 4 ++++
.../database/models/device/DeviceType.kt | 17 +++++++++++++++++
.../database/models/device/types/Chipolo.kt | 3 +++
.../database/models/device/types/Tile.kt | 4 +++-
.../database/repository/DeviceRepository.kt | 7 +++----
.../database/repository/ScanRepository.kt | 17 ++++++++---------
.../detection/TrackingDetectorWorker.kt | 2 +-
.../ui/dashboard/DeviceMapViewModel.kt | 2 +-
.../ui/dashboard/RiskDetailViewModel.kt | 5 ++---
.../ui/devices/AllDevicesViewModel.kt | 2 +-
.../ui/devices/DevicesFragment.kt | 2 +-
.../util/risk/RiskLevelEvaluator.kt | 12 ++++++++----
13 files changed, 56 insertions(+), 27 deletions(-)
diff --git a/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanDBTests.kt b/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanDBTests.kt
index 464758ab..26286ef8 100644
--- a/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanDBTests.kt
+++ b/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanDBTests.kt
@@ -112,7 +112,7 @@ class ScanDBTests() {
val updatedScan = scanRepository.scanWithId(scanId.toInt())
assert(updatedScan != null)
- assert(scanDB?.endDate == updatedScan?.endDate)
+ assert(scanDB.endDate == updatedScan?.endDate)
assert(updatedScan?.noDevicesFound == 10)
assert(updatedScan?.duration == 5)
}
@@ -123,7 +123,9 @@ class ScanDBTests() {
unfinished.forEach{
assert(it.endDate == null)
assert(it.startDate == null)
- assert(it.startDate?.compareTo(RiskLevelEvaluator.relevantTrackingDate) ?: -1 >= 0)
+ assert(
+ (it.startDate?.compareTo(RiskLevelEvaluator.relevantTrackingDateDefault) ?: -1) >= 0
+ )
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
index da1a6c05..6f0896ff 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
@@ -2,6 +2,7 @@ package de.seemoo.at_tracking_detection.database.models.device
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
+import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
interface DeviceContext {
val bluetoothFilter: ScanFilter
@@ -16,6 +17,9 @@ interface DeviceContext {
val statusByteDeviceType: UInt
+ val numberOfDaysToBeConsideredForTrackingDetection: Long
+ get() = RiskLevelEvaluator.RELEVANT_DAYS
+
fun getConnectionState(scanResult: ScanResult): ConnectionState {
return ConnectionState.UNKNOWN
}
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 7abc4279..3a1e56fd 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
@@ -3,6 +3,7 @@ package de.seemoo.at_tracking_detection.database.models.device
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.device.types.*
import de.seemoo.at_tracking_detection.util.SharedPrefs
+import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
enum class DeviceType {
UNKNOWN,
@@ -80,4 +81,20 @@ enum class DeviceType {
else -> false
}
}
+
+ fun getNumberOfDaysToBeConsideredForTrackingDetection(): Long {
+ return when (this) {
+ TILE -> Tile.numberOfDaysToBeConsideredForTrackingDetection
+ CHIPOLO -> Chipolo.numberOfDaysToBeConsideredForTrackingDetection
+ UNKNOWN -> Unknown.numberOfDaysToBeConsideredForTrackingDetection
+ AIRPODS -> AirPods.numberOfDaysToBeConsideredForTrackingDetection
+ AIRTAG -> AirTag.numberOfDaysToBeConsideredForTrackingDetection
+ APPLE -> AppleDevice.numberOfDaysToBeConsideredForTrackingDetection
+ FIND_MY -> FindMy.numberOfDaysToBeConsideredForTrackingDetection
+ SAMSUNG -> SamsungDevice.numberOfDaysToBeConsideredForTrackingDetection
+ GALAXY_SMART_TAG -> SmartTag.numberOfDaysToBeConsideredForTrackingDetection
+ GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfDaysToBeConsideredForTrackingDetection
+ else -> RiskLevelEvaluator.RELEVANT_DAYS
+ }
+ }
}
\ No newline at end of file
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 2329c13c..9ff15105 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
@@ -37,6 +37,9 @@ class Chipolo(val id: Int) : Device() {
override val statusByteDeviceType: UInt
get() = 0u
+ override val numberOfDaysToBeConsideredForTrackingDetection: Long
+ get() = 2
+
val offlineFindingServiceUUID: ParcelUuid = ParcelUuid.fromString("0000FE33-0000-1000-8000-00805F9B34FB")
override fun getConnectionState(scanResult: ScanResult): ConnectionState {
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 1c9aacc3..a12d35bf 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
@@ -6,7 +6,6 @@ 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 timber.log.Timber
class Tile(val id: Int) : Device(){
override val imageResource: Int
@@ -39,6 +38,9 @@ class Tile(val id: Int) : Device(){
override val statusByteDeviceType: UInt
get() = 0u
+ override val numberOfDaysToBeConsideredForTrackingDetection: Long
+ get() = 2
+
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/database/repository/DeviceRepository.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt
index 889e5331..01efcfdd 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt
@@ -5,7 +5,6 @@ import de.seemoo.at_tracking_detection.database.daos.DeviceDao
import de.seemoo.at_tracking_detection.database.relations.DeviceBeaconNotification
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.database.models.device.DeviceType
-import de.seemoo.at_tracking_detection.util.risk.RiskLevel
import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
import kotlinx.coroutines.flow.Flow
import java.time.LocalDateTime
@@ -47,12 +46,12 @@ class DeviceRepository @Inject constructor(private val deviceDao: DeviceDao) {
fun getDevice(deviceAddress: String): BaseDevice? = deviceDao.getByAddress(deviceAddress)
- val countNotTracking = deviceDao.getCountNotTracking(RiskLevelEvaluator.relevantTrackingDate)
+ val countNotTracking = deviceDao.getCountNotTracking(RiskLevelEvaluator.relevantTrackingDateDefault)
val countIgnored = deviceDao.getCountIgnored()
- fun countForDeviceType(deviceType: DeviceType) = deviceDao.getCountForType(deviceType.name, RiskLevelEvaluator.relevantTrackingDate)
- fun countForDeviceTypes(deviceType1: DeviceType, deviceType2: DeviceType) = deviceDao.getCountForTypes(deviceType1.name, deviceType2.name, RiskLevelEvaluator.relevantTrackingDate)
+ fun countForDeviceType(deviceType: DeviceType) = deviceDao.getCountForType(deviceType.name, RiskLevelEvaluator.relevantTrackingDateDefault)
+ fun countForDeviceTypes(deviceType1: DeviceType, deviceType2: DeviceType) = deviceDao.getCountForTypes(deviceType1.name, deviceType2.name, RiskLevelEvaluator.relevantTrackingDateDefault)
fun getNumberOfLocationsForDeviceSince(deviceAddress: String, since: LocalDateTime): Int = deviceDao.getNumberOfLocationsForDevice(deviceAddress, since)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/ScanRepository.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/ScanRepository.kt
index 74af3196..c33f43db 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/ScanRepository.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/ScanRepository.kt
@@ -5,7 +5,6 @@ import de.seemoo.at_tracking_detection.database.daos.ScanDao
import de.seemoo.at_tracking_detection.database.models.Scan
import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
import kotlinx.coroutines.flow.Flow
-import java.time.LocalDateTime
import javax.inject.Inject
class ScanRepository @Inject constructor(
@@ -15,16 +14,16 @@ class ScanRepository @Inject constructor(
var lastScan = scanDao.lastScan()
var relevantScans =
- scanDao.getScansSince(RiskLevelEvaluator.relevantTrackingDate)
+ scanDao.getScansSince(RiskLevelEvaluator.relevantTrackingDateDefault)
- fun relevantScans(manual: Boolean, limit: Int): List = scanDao.getScansSince(RiskLevelEvaluator.relevantTrackingDate, manual, limit)
+ fun relevantScans(manual: Boolean, limit: Int): List = scanDao.getScansSince(RiskLevelEvaluator.relevantTrackingDateDefault, manual, limit)
- val relevantDebugScans = scanDao.getDebugScansSince(RiskLevelEvaluator.relevantTrackingDate)
+ val relevantDebugScans = scanDao.getDebugScansSince(RiskLevelEvaluator.relevantTrackingDateDefault)
var flowRelevantScans =
- scanDao.getFlowScansSince(RiskLevelEvaluator.relevantTrackingDate)
+ scanDao.getFlowScansSince(RiskLevelEvaluator.relevantTrackingDateDefault)
- val flowDebugScans = scanDao.getFlowDebugRelevantScans(RiskLevelEvaluator.relevantTrackingDate)
+ val flowDebugScans = scanDao.getFlowDebugRelevantScans(RiskLevelEvaluator.relevantTrackingDateDefault)
var allScans: List = scanDao.getAllScans()
@@ -32,15 +31,15 @@ class ScanRepository @Inject constructor(
val totalCount: Int = scanDao.getNumberOfScans()
- var countInRelevantTime: Int = scanDao.getNumberOfScansSince(RiskLevelEvaluator.relevantTrackingDate)
+ var countInRelevantTime: Int = scanDao.getNumberOfScansSince(RiskLevelEvaluator.relevantTrackingDateDefault)
- val relevantUnfinishedScans: List = scanDao.unfinishedScans(RiskLevelEvaluator.relevantTrackingDate)
+ val relevantUnfinishedScans: List = scanDao.unfinishedScans(RiskLevelEvaluator.relevantTrackingDateDefault)
@WorkerThread
suspend fun insert(scan: Scan): Long = scanDao.insert(scan)
@WorkerThread
- suspend fun deleteIrrelevantScans() = scanDao.deleteUntil(RiskLevelEvaluator.relevantTrackingDate)
+ suspend fun deleteIrrelevantScans() = scanDao.deleteUntil(RiskLevelEvaluator.relevantTrackingDateDefault)
@WorkerThread
suspend fun update(scan: Scan) = scanDao.update(scan)
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 929fa273..1a978ba3 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
@@ -82,7 +82,7 @@ class TrackingDetectorWorker @AssistedInject constructor(
//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 {
// Only retrieve the last two weeks since they are only relevant for tracking
- val beacons = beaconRepository.getDeviceBeaconsSince(it.deviceAddress, RiskLevelEvaluator.relevantTrackingDate)
+ val beacons = beaconRepository.getDeviceBeaconsSince(it.deviceAddress, RiskLevelEvaluator.relevantTrackingDateDefault)
beaconsPerDevice[it.deviceAddress] = beacons
}
return beaconsPerDevice
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapViewModel.kt
index 1b5ef4d0..e2e511cf 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapViewModel.kt
@@ -17,7 +17,7 @@ class DeviceMapViewModel @Inject constructor(
beaconRepository.getDeviceBeacons(it)
}
- val allLocations: LiveData> = beaconRepository.getBeaconsSince(RiskLevelEvaluator.relevantTrackingDate).asLiveData()
+ val allLocations: LiveData> = beaconRepository.getBeaconsSince(RiskLevelEvaluator.relevantTrackingDateDefault).asLiveData()
val isMapLoading = MutableLiveData(false)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailViewModel.kt
index 3697037f..d64b8a5a 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailViewModel.kt
@@ -11,7 +11,6 @@ import de.seemoo.at_tracking_detection.R
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.models.Beacon
-import de.seemoo.at_tracking_detection.database.models.Location as LocationModel
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.database.repository.LocationRepository
import de.seemoo.at_tracking_detection.database.repository.ScanRepository
@@ -32,14 +31,14 @@ class RiskDetailViewModel @Inject constructor(
val locationRepository: LocationRepository,
) : ViewModel() {
- private val relevantDate = RiskLevelEvaluator.relevantTrackingDate
+ private val relevantDate = RiskLevelEvaluator.relevantTrackingDateDefault
private val trackersFound: List = deviceRepository.trackingDevicesNotIgnoredSince(relevantDate)
private val lastSeenDates = trackersFound.map {
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).format(it.lastSeen)
}
var riskColor: Int
- val numberOfTrackersFound = deviceRepository.trackingDevicesNotIgnoredSinceCount(RiskLevelEvaluator.relevantTrackingDate).asLiveData()
+ val numberOfTrackersFound = deviceRepository.trackingDevicesNotIgnoredSinceCount(RiskLevelEvaluator.relevantTrackingDateDefault).asLiveData()
val totalLocationsTrackedCount= locationRepository.locationsSinceCount(relevantDate).asLiveData()
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 49135171..2d6c4b86 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
@@ -20,7 +20,7 @@ class AllDevicesViewModel @Inject constructor(
val countNotTracking = deviceRepository.countNotTracking.asLiveData()
val countIgnored = deviceRepository.countIgnored.asLiveData()
- val countTracking = deviceRepository.trackingDevicesNotIgnoredSinceCount(RiskLevelEvaluator.relevantTrackingDate).asLiveData()
+ val countTracking = deviceRepository.trackingDevicesNotIgnoredSinceCount(RiskLevelEvaluator.relevantTrackingDateDefault).asLiveData()
val countAirTags = deviceRepository.countForDeviceType(DeviceType.AIRTAG).asLiveData()
val countFindMy = deviceRepository.countForDeviceType(DeviceType.FIND_MY).asLiveData()
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DevicesFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DevicesFragment.kt
index 7074b57d..70785a70 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DevicesFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DevicesFragment.kt
@@ -75,7 +75,7 @@ abstract class DevicesFragment(
)
)
} else {
- val relevantTrackingStartDate = RiskLevelEvaluator.relevantTrackingDate.toLocalDate()
+ val relevantTrackingStartDate = RiskLevelEvaluator.relevantTrackingDateDefault.toLocalDate()
devicesViewModel.addOrRemoveFilter(
DateRangeFilter.build(
relevantTrackingStartDate,
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
index e32d1f0b..1128b443 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
@@ -25,7 +25,7 @@ class RiskLevelEvaluator(
fun evaluateRiskLevel(): RiskLevel {
Timber.d("evaluateRiskLevel() called")
// val baseDevices: List = deviceRepository.trackingDevicesSince(relevantTrackingDate)
- val baseDevices: List = deviceRepository.trackingDevicesNotIgnoredSince(relevantTrackingDate)
+ val baseDevices: List = deviceRepository.trackingDevicesNotIgnoredSince(relevantTrackingDateDefault)
val totalTrackers = baseDevices.count()
@@ -67,7 +67,7 @@ class RiskLevelEvaluator(
* The date when a tracker has been discovered last
*/
fun getLastTrackerDiscoveryDate(): Date {
- val relevantDate = relevantTrackingDate
+ val relevantDate = relevantTrackingDateDefault
val baseDevices: List = deviceRepository.trackingDevicesSince(relevantDate)
.sortedByDescending { it.lastSeen }
@@ -86,7 +86,7 @@ class RiskLevelEvaluator(
companion object {
/** The number of days that we use to calculate the risk **/
- const val RELEVANT_DAYS: Long = 14 // Only consider beacons in the last x days
+ const val RELEVANT_DAYS: Long = 14 // Only consider beacons in the last x days, default value, can be overwritten in the specific device properties
private const val NUMBER_OF_NOTIFICATIONS_FOR_HIGH_RISK: Long = 2 // After x MEDIUM risk notifications (for a single device) change risk level to HIGH
private const val RELEVANT_DAYS_NOTIFICATIONS: Long = 5 // After MEDIUM risk notifications in the last x days (for a single device) change risk level to HIGH
private const val NUMBER_OF_LOCATIONS_BEFORE_ALARM: Int = 3 // Number of beacons with locations before notification is created
@@ -94,7 +94,7 @@ class RiskLevelEvaluator(
private const val MAX_ACCURACY_FOR_LOCATIONS: Float = 100.0F // Minimum Location accuracy for high risk
const val HOURS_AT_LEAST_UNTIL_NEXT_NOTIFICATION: Long = 8 // Minimum time difference until next notification
const val MAX_NUMBER_MEDIUM_RISK: Long = 3 // Maximum number of devices with MEDIUM risk until the total risk level is set to high
- val relevantTrackingDate: LocalDateTime = LocalDateTime.now().minusDays(RELEVANT_DAYS)
+ val relevantTrackingDateDefault: LocalDateTime = LocalDateTime.now().minusDays(RELEVANT_DAYS) // Fallback Option, if possible use getRelevantTrackingDate() Function
private val relevantNotificationDate: LocalDateTime = LocalDateTime.now().minusDays(RELEVANT_DAYS_NOTIFICATIONS)
// A single tracker gets tracked at least for x minutes until notification is created
@@ -106,6 +106,8 @@ class RiskLevelEvaluator(
getMinutesAtLeastTrackedBeforeAlarm()
)
+ private fun getRelevantTrackingDate(relevantDays: Long): LocalDateTime = LocalDateTime.now().minusDays(relevantDays)
+
fun getMinutesAtLeastTrackedBeforeAlarm(): Long {
return when (SharedPrefs.riskSensitivity) {
"low" -> MINUTES_AT_LEAST_TRACKED_BEFORE_ALARM_LOW
@@ -125,6 +127,8 @@ class RiskLevelEvaluator(
// Not ignored
// Tracker has been seen long enough
if (!device.ignore && device.firstDiscovery <= getAtLeastTrackedSince()) {
+ val relevantDays: Long = device.deviceType?.getNumberOfDaysToBeConsideredForTrackingDetection() ?: RELEVANT_DAYS
+ val relevantTrackingDate = getRelevantTrackingDate(relevantDays)
val numberOfBeacons = beaconRepository.getNumberOfBeaconsAddress(device.address, relevantTrackingDate)
// Detected at least 3 Times
From 973891ca22558416cc4b6f50c39050210e07c207 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Fri, 8 Dec 2023 10:59:05 +0100
Subject: [PATCH 034/154] upgrade gradle to 8.2.0
---
build.gradle | 2 +-
gradle/wrapper/gradle-wrapper.properties | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/build.gradle b/build.gradle
index 5d05bb3a..24822371 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,7 +13,7 @@ buildscript {
}
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.1.4'
+ classpath 'com.android.tools.build:gradle:8.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 368b1c73..b4af7876 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Wed Aug 02 18:45:44 CEST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
From 47657dbb96f50613b2a018a1c090744e4a9b91c9 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Fri, 8 Dec 2023 11:49:21 +0100
Subject: [PATCH 035/154] upgrade to Android 14 / SDK 34
---
app/build.gradle | 6 ++++--
app/src/main/AndroidManifest.xml | 19 +++++++++++++++++--
.../ui/dashboard/RallyLineGraphChart.kt | 8 ++++----
gradle.properties | 1 -
4 files changed, 25 insertions(+), 9 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index dd88b8ae..0cdf2ee7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -17,13 +17,15 @@ android {
viewBinding true
dataBinding true
compose true
+ buildConfig true
}
+ compileSdk = 34
+
defaultConfig {
applicationId "de.seemoo.at_tracking_detection"
minSdkVersion 28
- targetSdkVersion 33
- compileSdk 33
+ targetSdk = 34
versionCode 38
versionName "2.1"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 505363cd..5c300e76 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,6 +29,9 @@
+
+
+
+ android:exported="false">
+
+
+
+
+
-
+
+
+
+
+
+
Date: Mon, 11 Dec 2023 14:13:32 +0100
Subject: [PATCH 036/154] fix Crash when clicking on Scan Element
---
.../detection/BluetoothReceiver.kt | 3 ---
.../ui/dashboard/ArticleFragment.kt | 13 ++++++++-----
.../IgnoreBatteryOptimizationFragment.kt | 7 +------
.../ui/tracking/TrackingFragment.kt | 16 +++++++++++++++-
4 files changed, 24 insertions(+), 15 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/detection/BluetoothReceiver.kt b/app/src/main/java/de/seemoo/at_tracking_detection/detection/BluetoothReceiver.kt
index d10d5c03..97293da2 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/detection/BluetoothReceiver.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/detection/BluetoothReceiver.kt
@@ -5,8 +5,6 @@ import android.bluetooth.le.ScanResult
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import android.os.Build
-import androidx.annotation.RequiresApi
import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.database.repository.BeaconRepository
import de.seemoo.at_tracking_detection.database.repository.DeviceRepository
@@ -22,7 +20,6 @@ class BluetoothReceiver : BroadcastReceiver() {
@Inject
lateinit var deviceRepository: DeviceRepository
- @RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
TrackingDetectorConstants.BLUETOOTH_DEVICE_FOUND_ACTION -> {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
index 9a2e00fc..f52cc5d4 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
@@ -10,6 +10,7 @@ import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.MaterialTheme
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
@@ -43,7 +44,7 @@ class ArticleFragment : Fragment() {
authorTextView.text = author
articleReadingTimeView.text = context?.getString(R.string.article_reading_time, readingTime)
- var modifier = Modifier.fillMaxSize()
+ val modifier = Modifier.fillMaxSize()
val connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
@@ -57,10 +58,12 @@ class ArticleFragment : Fragment() {
markdownView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
- MarkDown(
- url = url,
- modifier = modifier
- )
+ MaterialTheme {
+ MarkDown(
+ url = url,
+ modifier = modifier
+ )
+ }
}
}
} catch (e: Exception) {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/IgnoreBatteryOptimizationFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/IgnoreBatteryOptimizationFragment.kt
index c2fc7364..fa7acb52 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/IgnoreBatteryOptimizationFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/IgnoreBatteryOptimizationFragment.kt
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
-import android.os.Build
import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
@@ -12,7 +11,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
-import androidx.annotation.RequiresApi
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import dagger.hilt.android.AndroidEntryPoint
@@ -41,16 +39,13 @@ class IgnoreBatteryOptimizationFragment : Fragment() {
val ignoreBatteryOptimizationButton =
view.findViewById(R.id.onboarding_ignore_battery_optimization_button)
ignoreBatteryOptimizationButton.setOnClickListener {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- requestIgnoreBatteryOptimization()
- }
+ requestIgnoreBatteryOptimization()
}
}
// This App fulfills the requirements to request ignore battery optimization. Further
// information can be found here:
// https://developer.android.com/training/monitoring-device-state/doze-standby.html#exemption-cases
- @RequiresApi(Build.VERSION_CODES.M)
@SuppressLint("BatteryLife")
private fun requestIgnoreBatteryOptimization() {
val intent = Intent()
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 83332cba..1faef9e9 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -1,6 +1,8 @@
package de.seemoo.at_tracking_detection.ui.tracking
+import android.annotation.SuppressLint
import android.content.*
+import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.transition.TransitionInflater
@@ -73,13 +75,25 @@ class TrackingFragment : Fragment() {
return binding.root
}
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onResume() {
super.onResume()
val activity = ATTrackingDetectionApplication.getCurrentActivity() ?: return
LocalBroadcastManager.getInstance(activity)
.registerReceiver(gattUpdateReceiver, DeviceManager.gattIntentFilter)
- activity.registerReceiver(gattUpdateReceiver, DeviceManager.gattIntentFilter)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ activity.registerReceiver(
+ gattUpdateReceiver,
+ DeviceManager.gattIntentFilter,
+ Context.RECEIVER_NOT_EXPORTED
+ )
+ } else {
+ activity.registerReceiver(
+ gattUpdateReceiver,
+ DeviceManager.gattIntentFilter
+ )
+ }
}
override fun onPause() {
From 7696ca075f91aacd06a660c881f4b11bfad4a131 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 11 Dec 2023 14:35:23 +0100
Subject: [PATCH 037/154] optimize BluetoothReceiver for Android 13 or higher
---
.../at_tracking_detection/detection/BluetoothReceiver.kt | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/detection/BluetoothReceiver.kt b/app/src/main/java/de/seemoo/at_tracking_detection/detection/BluetoothReceiver.kt
index 97293da2..7a7f37dc 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/detection/BluetoothReceiver.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/detection/BluetoothReceiver.kt
@@ -5,6 +5,7 @@ import android.bluetooth.le.ScanResult
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
+import android.os.Build
import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.database.repository.BeaconRepository
import de.seemoo.at_tracking_detection.database.repository.DeviceRepository
@@ -23,8 +24,11 @@ class BluetoothReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
TrackingDetectorConstants.BLUETOOTH_DEVICE_FOUND_ACTION -> {
- val scanResult =
- intent.getParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT)
+ val scanResult = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT, ScanResult::class.java)
+ } else {
+ intent.getParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT)
+ }
if (scanResult != null) {
for (result: ScanResult in scanResult) {
Timber.d("Found ${result.device.address}")
From 249a6d4cfe4ee79ae4ab82b24fe7eaab907df18d Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 11 Dec 2023 15:11:02 +0100
Subject: [PATCH 038/154] upgrade some plugins and dependencies
---
app/build.gradle | 26 +++++++++++++-------------
build.gradle | 6 +++---
2 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 0cdf2ee7..b67db716 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -94,13 +94,13 @@ dependencies {
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation 'com.github.bastienpaulfr:Treessence:1.0.0'
implementation 'androidx.work:work-runtime-ktx:2.8.1'
- implementation 'androidx.core:core-ktx:1.10.1'
+ implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'com.google.android.material:material:1.9.0'
+ implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
- implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
- implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
+ implementation 'androidx.navigation:navigation-fragment-ktx:2.7.5'
+ implementation 'androidx.navigation:navigation-ui-ktx:2.7.5'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
implementation 'androidx.preference:preference-ktx:1.2.1'
@@ -113,12 +113,12 @@ dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.work:work-testing:2.8.1'
- implementation 'androidx.core:core-ktx:1.10.1'
+ implementation 'androidx.core:core-ktx:1.12.0'
debugImplementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.5'
implementation "com.google.dagger:hilt-android:$hilt_version"
- implementation 'androidx.hilt:hilt-work:1.0.0'
- implementation 'androidx.hilt:hilt-navigation-fragment:1.0.0'
+ implementation 'androidx.hilt:hilt-work:1.1.0'
+ implementation 'androidx.hilt:hilt-navigation-fragment:1.1.0'
implementation 'com.github.AppIntro:AppIntro:6.1.0'
@@ -142,7 +142,7 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
- androidTestImplementation 'androidx.room:room-testing:2.5.2'
+ androidTestImplementation "androidx.room:room-testing:$room_version"
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
@@ -157,17 +157,17 @@ dependencies {
//Compose
// Integration with activities
- implementation 'androidx.activity:activity-compose:1.7.2'
+ implementation 'androidx.activity:activity-compose:1.8.1'
// Compose Material Design
- implementation 'androidx.compose.material:material:1.4.3'
+ implementation 'androidx.compose.material:material:1.5.4'
// Animations
- implementation 'androidx.compose.animation:animation:1.4.3'
+ implementation 'androidx.compose.animation:animation:1.5.4'
// Tooling support (Previews, etc.)
- implementation 'androidx.compose.ui:ui-tooling:1.4.3'
+ implementation 'androidx.compose.ui:ui-tooling:1.5.4'
// Integration with ViewModels
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
// UI Tests
- androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.4.3'
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.5.4'
// When using a MDC theme
implementation "com.google.android.material:compose-theme-adapter:1.2.1"
diff --git a/build.gradle b/build.gradle
index 24822371..22e846d2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,8 +1,8 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.8.22'
- ext.hilt_version = '2.47'
- ext.room_version = '2.5.2'
+ ext.hilt_version = '2.48'
+ ext.room_version = '2.6.1'
ext.compose_version = '1.1.1'
ext.about_libraries_version = '10.8.3'
repositories {
@@ -32,6 +32,6 @@ allprojects {
}
}
-task clean(type: Delete) {
+tasks.register('clean', Delete) {
delete rootProject.buildDir
}
\ No newline at end of file
From a93b6878462a11444707585d56ec1807ecc57f6f Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 18 Dec 2023 16:48:10 +0100
Subject: [PATCH 039/154] UI improvements: Manual Scan now more user friendly,
more explanation, less technical terms
---
.../ui/scan/ScanDistanceFragment.kt | 60 +++++++++++------
.../res/layout/fragment_scan_distance.xml | 65 ++++++++++++-------
app/src/main/res/values-de/strings.xml | 7 +-
app/src/main/res/values/strings.xml | 12 ++--
4 files changed, 92 insertions(+), 52 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
index 8ae0e374..fde2a35a 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
@@ -20,7 +20,6 @@ import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getBatteryState
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getBatteryStateAsString
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getConnectionState
-import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getConnectionStateAsString
import de.seemoo.at_tracking_detection.util.ble.BLEScanner
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
import de.seemoo.at_tracking_detection.database.models.device.BatteryState
@@ -55,9 +54,10 @@ class ScanDistanceFragment : Fragment() {
if (getPublicKey(it) == publicKey){
// viewModel.bluetoothRssi.postValue(it.rssi)
val connectionState = getConnectionState(it)
- val connectionStateString = getConnectionStateAsString(it)
- viewModel.connectionStateString.postValue(connectionStateString)
viewModel.connectionState.postValue(connectionState)
+ val connectionStateString = getConnectionStateExplanation(connectionState)
+ viewModel.connectionStateString.postValue(connectionStateString)
+
val batteryState = getBatteryState(it)
val batteryStateString = getBatteryStateAsString(it)
viewModel.batteryStateString.postValue(batteryStateString)
@@ -73,7 +73,7 @@ class ScanDistanceFragment : Fragment() {
if (viewModel.isFirstScanCallback.value as Boolean) {
viewModel.isFirstScanCallback.value = false
- removeSearchMessage()
+ removeSearchMessage(batteryState != BatteryState.UNKNOWN)
}
}
@@ -94,14 +94,19 @@ class ScanDistanceFragment : Fragment() {
}
}
- private fun removeSearchMessage() {
+ private fun removeSearchMessage(showBattery: Boolean = true) {
binding.scanResultLoadingBar.visibility = View.GONE
binding.searchingForDevice.visibility = View.GONE
binding.connectionQuality.visibility = View.VISIBLE
- binding.batteryLayout.visibility = View.VISIBLE
binding.deviceTypeLayout.visibility = View.VISIBLE
binding.connectionStateLayout.visibility = View.VISIBLE
+ binding.scanExplanationLayout.visibility = View.VISIBLE
binding.deviceNotFound.visibility = View.GONE
+ if (showBattery) {
+ binding.batteryLayout.visibility = View.VISIBLE
+ } else {
+ binding.batteryLayout.visibility = View.GONE
+ }
}
private fun showSearchMessage() {
@@ -109,6 +114,7 @@ class ScanDistanceFragment : Fragment() {
binding.searchingForDevice.visibility = View.VISIBLE
binding.connectionQuality.visibility = View.GONE
binding.batteryLayout.visibility = View.GONE
+ binding.scanExplanationLayout.visibility = View.GONE
binding.deviceTypeLayout.visibility = View.GONE
binding.connectionStateLayout.visibility = View.GONE
binding.deviceNotFound.visibility = View.GONE
@@ -118,6 +124,7 @@ class ScanDistanceFragment : Fragment() {
binding.scanResultLoadingBar.visibility = View.GONE
binding.searchingForDevice.visibility = View.GONE
binding.connectionQuality.visibility = View.GONE
+ binding.scanExplanationLayout.visibility = View.GONE
binding.batteryLayout.visibility = View.GONE
binding.deviceTypeLayout.visibility = View.GONE
binding.connectionStateLayout.visibility = View.GONE
@@ -147,12 +154,13 @@ class ScanDistanceFragment : Fragment() {
}
private fun setBattery(batteryState: BatteryState) {
+ binding.batteryLayout.visibility = View.VISIBLE
when(batteryState) {
BatteryState.FULL -> binding.batterySymbol.setImageDrawable(resources.getDrawable(R.drawable.ic_battery_full_24))
BatteryState.MEDIUM -> binding.batterySymbol.setImageDrawable(resources.getDrawable(R.drawable.ic_battery_medium_24))
BatteryState.LOW -> binding.batterySymbol.setImageDrawable(resources.getDrawable(R.drawable.ic_battery_low_24))
BatteryState.VERY_LOW -> binding.batterySymbol.setImageDrawable(resources.getDrawable(R.drawable.ic_battery_very_low_24))
- else -> binding.batterySymbol.setImageDrawable(resources.getDrawable(R.drawable.ic_battery_unknown_24))
+ else -> binding.batteryLayout.visibility = View.GONE
}
}
@@ -162,6 +170,16 @@ class ScanDistanceFragment : Fragment() {
binding.deviceTypeText.text = DeviceType.userReadableName(deviceType)
}
+ private fun getConnectionStateExplanation(connectionState: ConnectionState): String {
+ return when (connectionState) {
+ ConnectionState.OVERMATURE_OFFLINE -> getString(R.string.connection_state_overmature_offline_explanation)
+ ConnectionState.CONNECTED -> getString(R.string.connection_state_connected_explanation)
+ ConnectionState.OFFLINE -> getString(R.string.connection_state_offline_explanation)
+ ConnectionState.PREMATURE_OFFLINE -> getString(R.string.connection_state_premature_offline_explanation)
+ ConnectionState.UNKNOWN -> getString(R.string.connection_state_unknown_explanation)
+ }
+ }
+
private fun startBluetoothScan() {
// Start a scan if the BLEScanner is not already running
if (!BLEScanner.isScanning) {
@@ -210,20 +228,20 @@ class ScanDistanceFragment : Fragment() {
startBluetoothScan()
- val infoButton = binding.infoButton
- infoButton.setOnClickListener {
- val text = when (viewModel.connectionState.value as ConnectionState){
- ConnectionState.OVERMATURE_OFFLINE -> R.string.connection_state_overmature_offline_explanation
- ConnectionState.CONNECTED -> R.string.connection_state_connected_explanation
- ConnectionState.OFFLINE -> R.string.connection_state_offline_explanation
- ConnectionState.PREMATURE_OFFLINE -> R.string.connection_state_premature_offline_explanation
- ConnectionState.UNKNOWN -> R.string.connection_state_unknown_explanation
- }
- val duration = Toast.LENGTH_SHORT
-
- val toast = Toast.makeText(requireContext(), text, duration) // in Activity
- toast.show()
- }
+// val infoButton = binding.infoButton
+// infoButton.setOnClickListener {
+// val text = when (viewModel.connectionState.value as ConnectionState){
+// ConnectionState.OVERMATURE_OFFLINE -> R.string.connection_state_overmature_offline_explanation
+// ConnectionState.CONNECTED -> R.string.connection_state_connected_explanation
+// ConnectionState.OFFLINE -> R.string.connection_state_offline_explanation
+// ConnectionState.PREMATURE_OFFLINE -> R.string.connection_state_premature_offline_explanation
+// ConnectionState.UNKNOWN -> R.string.connection_state_unknown_explanation
+// }
+// val duration = Toast.LENGTH_SHORT
+//
+// val toast = Toast.makeText(requireContext(), text, duration) // in Activity
+// toast.show()
+// }
val batterySymbol = binding.batterySymbol
batterySymbol.setOnClickListener {
diff --git a/app/src/main/res/layout/fragment_scan_distance.xml b/app/src/main/res/layout/fragment_scan_distance.xml
index f8a463b5..82e6257d 100644
--- a/app/src/main/res/layout/fragment_scan_distance.xml
+++ b/app/src/main/res/layout/fragment_scan_distance.xml
@@ -1,7 +1,6 @@
+ xmlns:bind="http://schemas.android.com/apk/res-auto">
+
+
+
+
+
@@ -91,6 +116,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/battery_status_text"
+ android:textSize="16sp"
bind:layout_constraintLeft_toLeftOf="parent"
bind:layout_constraintRight_toLeftOf="@id/battery_symbol"
bind:layout_constraintBottom_toBottomOf="parent"
@@ -111,33 +137,28 @@
+ bind:layout_constraintTop_toBottomOf="@id/battery_layout"
+ bind:layout_constraintEnd_toEndOf="parent"
+ bind:layout_constraintStart_toStartOf="parent">
-
-
+ bind:layout_constraintEnd_toEndOf="parent"
+ bind:layout_constraintStart_toStartOf="parent"
+ bind:layout_constraintWidth_default="wrap"
+ bind:layout_constraintHorizontal_bias="0.5"
+ bind:layout_constraintHorizontal_chainStyle="packed"/>
Sehr Niedrig
Unbekannt
Offline
- Gerät war in den letzten 15 Minuten nicht verbunden
+ Gerät ist vom Besitzer getrennt
Overmature Offline
Premature Offline
- Gerät war für mehrere Stunden nicht verbunden
+ Gerät ist seit mind. 8 Stunden vom Besitzer getrennt
Verbunden
Gerät ist verbunden mit Besitzer
- Gerät war in den letzten 15 Minuten verbunden
+ Gerät war in den letzten 15 Minuten mit Besitzer verbunden
Unbekannt
Startet oder Stoppt den Scan
nach Signalstärke
@@ -277,6 +277,7 @@
Verbindungsstatus kann nicht bestimmt werden
Informationen über den Verbindungsstatus des Geräts
Gerät ist nicht in Reichweite
+ Bewege dich langsam und versuche die Signalstäre zu maximieren um den Tracker zu finden.
Apple Geräte
FindMy Geräte
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4a293121..7642ac84 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -283,16 +283,16 @@
Unknown
Offline
- Device has not been connected for more than 15 minutes
+ Device is separated from its owner
Overmature Offline
- Device has not been connected to an owner device for a few hours
+ Device sis separated from its owner since at least 8 hours
Premature Offline
- Device has been connected in the last 15 minutes
+ Connection to owner in the last 15 minutes
Connected
- Device is currently connected to owner
+ Device is currently connected to its owner
Unknown
Information about the connection State of the device
-
+ Connection state of the tracker cannot be determined
Starts or Stops the Scan
by Signal Strength
@@ -303,13 +303,13 @@
Device is not in range
This icon shows the battery status of the tracker
Clicking on this icon shows an explanation for the connection status
+ Move slowly and try to maximize the signal strength to find the tracker.
Plays a sound on the tracking device
This device has been identified as a tracker
Searching for device
Battery:
- Connection State cannot be determined
Airtag
Airpods
From 24066eca399e2e5b3602169731913e898c92b135 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 18 Dec 2023 17:07:30 +0100
Subject: [PATCH 040/154] adjust articles for in production Server files
---
.../ui/dashboard/Article.kt | 6 +-----
.../ui/dashboard/ArticleFragment.kt | 21 ++++++++++++-------
.../ui/dashboard/DashboardRiskFragment.kt | 2 +-
app/src/main/res/values-de/strings.xml | 2 +-
app/src/main/res/values/strings.xml | 2 +-
5 files changed, 18 insertions(+), 15 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
index 9c88b4cd..848aa18d 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/Article.kt
@@ -27,11 +27,7 @@ fun parseArticles(jsonString: String): List {
return articleMap.values.toList()
}
-fun getURL(filename: String): URL {
- return URL("https://tpe.seemoo.tu-darmstadt.de/static/articles/$filename")
-}
-
-fun getImageURL(filename: String): String {
+fun getURL(filename: String): String {
return "https://tpe.seemoo.tu-darmstadt.de/articles/$filename"
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
index 9a2e00fc..cb2d9a61 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/ArticleFragment.kt
@@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
+import java.net.URL
import com.mukesh.MarkDown
import de.seemoo.at_tracking_detection.R
import timber.log.Timber
@@ -27,6 +28,10 @@ class ArticleFragment : Fragment() {
// Inflate the fragment layout
val view = inflater.inflate(R.layout.fragment_article, container, false)
+ fun errorHandling() {
+ Toast.makeText(requireContext(), "No internet connection. Cannot load article.", Toast.LENGTH_SHORT).show()
+ }
+
val titleTextView = view.findViewById(R.id.article_title)
val authorTextView = view.findViewById(R.id.article_author)
val markdownView = view.findViewById(R.id.markdown_view)
@@ -37,28 +42,30 @@ class ArticleFragment : Fragment() {
val readingTime = arguments?.getInt("readingTime")
val filename = arguments?.getString("filename")
- val url = getURL(filename!!)
+ if (filename == null) {
+ Timber.e("Filename is null")
+ errorHandling()
+ return view
+ }
+
+ val url = getURL(filename)
titleTextView.text = title
authorTextView.text = author
articleReadingTimeView.text = context?.getString(R.string.article_reading_time, readingTime)
- var modifier = Modifier.fillMaxSize()
+ val modifier = Modifier.fillMaxSize()
val connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
- fun errorHandling() {
- Toast.makeText(requireContext(), "No internet connection. Cannot load article.", Toast.LENGTH_SHORT).show()
- }
-
if ((networkCapabilities != null) && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
try {
markdownView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
MarkDown(
- url = url,
+ url = URL(url),
modifier = modifier
)
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
index 69cb167f..1ca69b9f 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
@@ -111,7 +111,7 @@ class DashboardRiskFragment : Fragment() {
}
if (article.preview_image.isNotEmpty()) { // TODO: Rename when in production to PreviewImage, also in JSON
- val imageURL = getImageURL(article.preview_image) // TODO: Rename when in production to PreviewImage, also in JSON
+ val imageURL = getURL(article.preview_image) // TODO: Rename when in production to PreviewImage, also in JSON
context?.let {
Glide.with(it)
.load(imageURL)
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 127191a7..ec021049 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -311,7 +311,7 @@
SmartTag (mit UWB)
- https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json
+ https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles_de.json
Lesedauer: %d min
Keine Internetverbindung
Es besteht aktuell keine Internetverbindung. Hier werden Artikel angezeigt, die dir helfen die App zu bedienen, sobald du wieder mit dem Internet verbunden bist.
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4a293121..9ccffba8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -348,7 +348,7 @@
Server is not reachable. Try again later.
No data to delete
- https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles.json
+ https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles_en.json
Reading time: %d min
No internet connection
You are currently not connected to the internet. You will find articles that will help you navigate the app here when you are connected to the internet.
From c67bed3e5ea964162eb263458337473f92a8ccfd Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Fri, 22 Dec 2023 16:08:05 +0100
Subject: [PATCH 041/154] number of Locations can be set individually for each
tracker, in default number of Locations is now depending on detection
sensitivity
---
.../database/models/device/DeviceContext.kt | 3 +++
.../database/models/device/DeviceType.kt | 17 +++++++++++++++--
.../util/risk/RiskLevelEvaluator.kt | 19 ++++++++++++++++---
3 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
index 6f0896ff..8f528d2c 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
@@ -20,6 +20,9 @@ interface DeviceContext {
val numberOfDaysToBeConsideredForTrackingDetection: Long
get() = RiskLevelEvaluator.RELEVANT_DAYS
+ val numberOfLocationsToBeConsideredForTrackingDetection: Int
+ get() = RiskLevelEvaluator.getLocationsAtLeastTrackedBeforeAlarm()
+
fun getConnectionState(scanResult: ScanResult): ConnectionState {
return ConnectionState.UNKNOWN
}
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 3a1e56fd..8a09bd49 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
@@ -3,7 +3,6 @@ package de.seemoo.at_tracking_detection.database.models.device
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.device.types.*
import de.seemoo.at_tracking_detection.util.SharedPrefs
-import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
enum class DeviceType {
UNKNOWN,
@@ -94,7 +93,21 @@ enum class DeviceType {
SAMSUNG -> SamsungDevice.numberOfDaysToBeConsideredForTrackingDetection
GALAXY_SMART_TAG -> SmartTag.numberOfDaysToBeConsideredForTrackingDetection
GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfDaysToBeConsideredForTrackingDetection
- else -> RiskLevelEvaluator.RELEVANT_DAYS
+ }
+ }
+
+ fun getNumberOfLocationsToBeConsideredForTrackingDetection(): Int {
+ return when (this) {
+ TILE -> Tile.numberOfLocationsToBeConsideredForTrackingDetection
+ CHIPOLO -> Chipolo.numberOfLocationsToBeConsideredForTrackingDetection
+ UNKNOWN -> Unknown.numberOfLocationsToBeConsideredForTrackingDetection
+ AIRPODS -> AirPods.numberOfLocationsToBeConsideredForTrackingDetection
+ AIRTAG -> AirTag.numberOfLocationsToBeConsideredForTrackingDetection
+ APPLE -> AppleDevice.numberOfLocationsToBeConsideredForTrackingDetection
+ FIND_MY -> FindMy.numberOfLocationsToBeConsideredForTrackingDetection
+ SAMSUNG -> SamsungDevice.numberOfLocationsToBeConsideredForTrackingDetection
+ GALAXY_SMART_TAG -> SmartTag.numberOfLocationsToBeConsideredForTrackingDetection
+ GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfLocationsToBeConsideredForTrackingDetection
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
index 1128b443..21e48f5a 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
@@ -89,7 +89,6 @@ class RiskLevelEvaluator(
const val RELEVANT_DAYS: Long = 14 // Only consider beacons in the last x days, default value, can be overwritten in the specific device properties
private const val NUMBER_OF_NOTIFICATIONS_FOR_HIGH_RISK: Long = 2 // After x MEDIUM risk notifications (for a single device) change risk level to HIGH
private const val RELEVANT_DAYS_NOTIFICATIONS: Long = 5 // After MEDIUM risk notifications in the last x days (for a single device) change risk level to HIGH
- private const val NUMBER_OF_LOCATIONS_BEFORE_ALARM: Int = 3 // Number of beacons with locations before notification is created
private const val NUMBER_OF_BEACONS_BEFORE_ALARM: Int = 3 // Number of total beacons before notification is created
private const val MAX_ACCURACY_FOR_LOCATIONS: Float = 100.0F // Minimum Location accuracy for high risk
const val HOURS_AT_LEAST_UNTIL_NEXT_NOTIFICATION: Long = 8 // Minimum time difference until next notification
@@ -102,6 +101,11 @@ class RiskLevelEvaluator(
private const val MINUTES_AT_LEAST_TRACKED_BEFORE_ALARM_MEDIUM: Long = 60
private const val MINUTES_AT_LEAST_TRACKED_BEFORE_ALARM_LOW: Long = 90
+ // A single tracker gets tracked at least for x minutes until notification is created
+ private const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_HIGH: Int = 3
+ private const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM: Int = 5
+ private const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW: Int = 8
+
private fun getAtLeastTrackedSince(): LocalDateTime = LocalDateTime.now().minusMinutes(
getMinutesAtLeastTrackedBeforeAlarm()
)
@@ -117,6 +121,15 @@ class RiskLevelEvaluator(
}
}
+ fun getLocationsAtLeastTrackedBeforeAlarm(): Int {
+ return when (SharedPrefs.riskSensitivity) {
+ "low" -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW
+ "medium" -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM
+ "high" -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_HIGH
+ else -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM
+ }
+ }
+
// Checks if BaseDevice is a tracking device
// Goes through all the criteria
fun checkRiskLevelForDevice(device: BaseDevice, useLocation: Boolean): RiskLevel {
@@ -157,7 +170,7 @@ class RiskLevelEvaluator(
val numberOfLocations = deviceRepository.getNumberOfLocationsForDeviceSince(device.address, relevantTrackingDate)
// Detected at at least 3 different locations
- if (!useLocation || numberOfLocations >= NUMBER_OF_LOCATIONS_BEFORE_ALARM) {
+ if (!useLocation || numberOfLocations >= (device.deviceType?.getNumberOfLocationsToBeConsideredForTrackingDetection() ?: getLocationsAtLeastTrackedBeforeAlarm())) {
val notificationRepository = ATTrackingDetectionApplication.getCurrentApp()?.notificationRepository ?: return RiskLevel.LOW
val falseAlarms = notificationRepository.getFalseAlarmForDeviceSinceCount(device.address, relevantTrackingDate)
@@ -174,7 +187,7 @@ class RiskLevelEvaluator(
// High Risk: High Number of Notifications and Accurate Locations
// Medium Risk: Low Number of Notifications or only inaccurate Location Reports
- return if (numberOfNotifications >= NUMBER_OF_NOTIFICATIONS_FOR_HIGH_RISK && numberOfLocationsWithAccuracyLimit >= NUMBER_OF_LOCATIONS_BEFORE_ALARM) {
+ return if (numberOfNotifications >= NUMBER_OF_NOTIFICATIONS_FOR_HIGH_RISK && numberOfLocationsWithAccuracyLimit >= (device.deviceType?.getNumberOfLocationsToBeConsideredForTrackingDetection() ?: getLocationsAtLeastTrackedBeforeAlarm())) {
deviceRepository.updateRiskLevelCache(device.address, 2, LocalDateTime.now())
RiskLevel.HIGH
} else{
From 27f8e3183bafe32e0dad69d560b9d36c6aaf5a64 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 2 Jan 2024 13:08:26 +0100
Subject: [PATCH 042/154] remove own location on map
---
.../at_tracking_detection/util/Utility.kt | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
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 1afc3d90..17313a97 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
@@ -77,16 +77,16 @@ object Utility {
fun enableMyLocationOverlay(
map: MapView
) {
- val context = ATTrackingDetectionApplication.getAppContext()
val locationOverlay = MyLocationNewOverlay(map)
- val options = BitmapFactory.Options()
- val bitmapPerson =
- BitmapFactory.decodeResource(context.resources, R.drawable.mylocation, options)
- locationOverlay.setPersonIcon(bitmapPerson)
- locationOverlay.setPersonHotspot((26.0 * 1.6).toFloat(), (26.0 * 1.6).toFloat())
- locationOverlay.setDirectionArrow(bitmapPerson, bitmapPerson)
- locationOverlay.enableMyLocation()
- locationOverlay.enableFollowLocation()
+// val context = ATTrackingDetectionApplication.getAppContext()
+// val options = BitmapFactory.Options()
+// val bitmapPerson =
+// BitmapFactory.decodeResource(context.resources, R.drawable.mylocation, options)
+// locationOverlay.setPersonIcon(bitmapPerson)
+// locationOverlay.setPersonHotspot((26.0 * 1.6).toFloat(), (26.0 * 1.6).toFloat())
+// locationOverlay.setDirectionArrow(bitmapPerson, bitmapPerson)
+// locationOverlay.enableMyLocation()
+// locationOverlay.enableFollowLocation()
map.overlays.add(locationOverlay)
map.controller.setZoom(ZOOMED_OUT_LEVEL)
}
From ec5c2d059c3c1515aafbcf714f4b2499d8263189 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 2 Jan 2024 13:28:25 +0100
Subject: [PATCH 043/154] NumberofLocations override can now be overwritten for
every RiskLevel
---
.../database/models/device/DeviceContext.kt | 10 +++-
.../database/models/device/DeviceType.kt | 52 +++++++++++++++----
.../util/risk/RiskLevelEvaluator.kt | 36 ++++++++-----
3 files changed, 72 insertions(+), 26 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
index 8f528d2c..5121bef3 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
@@ -20,8 +20,14 @@ interface DeviceContext {
val numberOfDaysToBeConsideredForTrackingDetection: Long
get() = RiskLevelEvaluator.RELEVANT_DAYS
- val numberOfLocationsToBeConsideredForTrackingDetection: Int
- get() = RiskLevelEvaluator.getLocationsAtLeastTrackedBeforeAlarm()
+ val numberOfLocationsToBeConsideredForTrackingDetectionLow: Int
+ get() = RiskLevelEvaluator.NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW
+
+ val numberOfLocationsToBeConsideredForTrackingDetectionMedium: Int
+ get() = RiskLevelEvaluator.NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM
+
+ val numberOfLocationsToBeConsideredForTrackingDetectionHigh: Int
+ get() = RiskLevelEvaluator.NUMBER_OF_LOCATIONS_BEFORE_ALARM_HIGH
fun getConnectionState(scanResult: ScanResult): ConnectionState {
return ConnectionState.UNKNOWN
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 8a09bd49..ed83385d 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
@@ -96,18 +96,48 @@ enum class DeviceType {
}
}
- fun getNumberOfLocationsToBeConsideredForTrackingDetection(): Int {
+ fun getNumberOfLocationsToBeConsideredForTrackingDetectionLow(): Int {
return when (this) {
- TILE -> Tile.numberOfLocationsToBeConsideredForTrackingDetection
- CHIPOLO -> Chipolo.numberOfLocationsToBeConsideredForTrackingDetection
- UNKNOWN -> Unknown.numberOfLocationsToBeConsideredForTrackingDetection
- AIRPODS -> AirPods.numberOfLocationsToBeConsideredForTrackingDetection
- AIRTAG -> AirTag.numberOfLocationsToBeConsideredForTrackingDetection
- APPLE -> AppleDevice.numberOfLocationsToBeConsideredForTrackingDetection
- FIND_MY -> FindMy.numberOfLocationsToBeConsideredForTrackingDetection
- SAMSUNG -> SamsungDevice.numberOfLocationsToBeConsideredForTrackingDetection
- GALAXY_SMART_TAG -> SmartTag.numberOfLocationsToBeConsideredForTrackingDetection
- GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfLocationsToBeConsideredForTrackingDetection
+ TILE -> Tile.numberOfLocationsToBeConsideredForTrackingDetectionLow
+ CHIPOLO -> Chipolo.numberOfLocationsToBeConsideredForTrackingDetectionLow
+ UNKNOWN -> Unknown.numberOfLocationsToBeConsideredForTrackingDetectionLow
+ AIRPODS -> AirPods.numberOfLocationsToBeConsideredForTrackingDetectionLow
+ AIRTAG -> AirTag.numberOfLocationsToBeConsideredForTrackingDetectionLow
+ APPLE -> AppleDevice.numberOfLocationsToBeConsideredForTrackingDetectionLow
+ FIND_MY -> FindMy.numberOfLocationsToBeConsideredForTrackingDetectionLow
+ SAMSUNG -> SamsungDevice.numberOfLocationsToBeConsideredForTrackingDetectionLow
+ GALAXY_SMART_TAG -> SmartTag.numberOfLocationsToBeConsideredForTrackingDetectionLow
+ GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfLocationsToBeConsideredForTrackingDetectionLow
+ }
+ }
+
+ fun getNumberOfLocationsToBeConsideredForTrackingDetectionMedium(): Int {
+ return when (this) {
+ TILE -> Tile.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+ CHIPOLO -> Chipolo.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+ UNKNOWN -> Unknown.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+ AIRPODS -> AirPods.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+ AIRTAG -> AirTag.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+ APPLE -> AppleDevice.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+ FIND_MY -> FindMy.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+ SAMSUNG -> SamsungDevice.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+ GALAXY_SMART_TAG -> SmartTag.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+ GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+ }
+ }
+
+ fun getNumberOfLocationsToBeConsideredForTrackingDetectionHigh(): Int {
+ return when (this) {
+ TILE -> Tile.numberOfLocationsToBeConsideredForTrackingDetectionHigh
+ CHIPOLO -> Chipolo.numberOfLocationsToBeConsideredForTrackingDetectionHigh
+ UNKNOWN -> Unknown.numberOfLocationsToBeConsideredForTrackingDetectionHigh
+ AIRPODS -> AirPods.numberOfLocationsToBeConsideredForTrackingDetectionHigh
+ AIRTAG -> AirTag.numberOfLocationsToBeConsideredForTrackingDetectionHigh
+ APPLE -> AppleDevice.numberOfLocationsToBeConsideredForTrackingDetectionHigh
+ FIND_MY -> FindMy.numberOfLocationsToBeConsideredForTrackingDetectionHigh
+ SAMSUNG -> SamsungDevice.numberOfLocationsToBeConsideredForTrackingDetectionHigh
+ GALAXY_SMART_TAG -> SmartTag.numberOfLocationsToBeConsideredForTrackingDetectionHigh
+ GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfLocationsToBeConsideredForTrackingDetectionHigh
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
index 21e48f5a..2cc4ea21 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
@@ -5,6 +5,7 @@ import de.seemoo.at_tracking_detection.database.models.Beacon
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.models.device.BaseDevice
+import de.seemoo.at_tracking_detection.database.models.device.DeviceType
import de.seemoo.at_tracking_detection.database.repository.NotificationRepository
import de.seemoo.at_tracking_detection.util.SharedPrefs
import timber.log.Timber
@@ -96,15 +97,15 @@ class RiskLevelEvaluator(
val relevantTrackingDateDefault: LocalDateTime = LocalDateTime.now().minusDays(RELEVANT_DAYS) // Fallback Option, if possible use getRelevantTrackingDate() Function
private val relevantNotificationDate: LocalDateTime = LocalDateTime.now().minusDays(RELEVANT_DAYS_NOTIFICATIONS)
- // A single tracker gets tracked at least for x minutes until notification is created
+ // Default Values: A single tracker gets tracked at least for x minutes until notification is created
private const val MINUTES_AT_LEAST_TRACKED_BEFORE_ALARM_HIGH: Long = 30
private const val MINUTES_AT_LEAST_TRACKED_BEFORE_ALARM_MEDIUM: Long = 60
private const val MINUTES_AT_LEAST_TRACKED_BEFORE_ALARM_LOW: Long = 90
- // A single tracker gets tracked at least for x minutes until notification is created
- private const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_HIGH: Int = 3
- private const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM: Int = 5
- private const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW: Int = 8
+ // Default Values: A single tracker gets tracked at least for x minutes until notification is created
+ const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_HIGH: Int = 3
+ const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM: Int = 5
+ const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW: Int = 8
private fun getAtLeastTrackedSince(): LocalDateTime = LocalDateTime.now().minusMinutes(
getMinutesAtLeastTrackedBeforeAlarm()
@@ -121,12 +122,21 @@ class RiskLevelEvaluator(
}
}
- fun getLocationsAtLeastTrackedBeforeAlarm(): Int {
- return when (SharedPrefs.riskSensitivity) {
- "low" -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW
- "medium" -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM
- "high" -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_HIGH
- else -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM
+ private fun getNumberOfLocationsToBeConsideredForTrackingDetection(deviceType: DeviceType?): Int {
+ return if (deviceType == null) {
+ when (SharedPrefs.riskSensitivity) {
+ "low" -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW
+ "medium" -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM
+ "high" -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_HIGH
+ else -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM
+ }
+ } else {
+ when (SharedPrefs.riskSensitivity) {
+ "low" -> deviceType.getNumberOfLocationsToBeConsideredForTrackingDetectionLow()
+ "medium" -> deviceType.getNumberOfLocationsToBeConsideredForTrackingDetectionMedium()
+ "high" -> deviceType.getNumberOfLocationsToBeConsideredForTrackingDetectionHigh()
+ else -> deviceType.getNumberOfLocationsToBeConsideredForTrackingDetectionMedium()
+ }
}
}
@@ -170,7 +180,7 @@ class RiskLevelEvaluator(
val numberOfLocations = deviceRepository.getNumberOfLocationsForDeviceSince(device.address, relevantTrackingDate)
// Detected at at least 3 different locations
- if (!useLocation || numberOfLocations >= (device.deviceType?.getNumberOfLocationsToBeConsideredForTrackingDetection() ?: getLocationsAtLeastTrackedBeforeAlarm())) {
+ if (!useLocation || numberOfLocations >= getNumberOfLocationsToBeConsideredForTrackingDetection(device.deviceType)) {
val notificationRepository = ATTrackingDetectionApplication.getCurrentApp()?.notificationRepository ?: return RiskLevel.LOW
val falseAlarms = notificationRepository.getFalseAlarmForDeviceSinceCount(device.address, relevantTrackingDate)
@@ -187,7 +197,7 @@ class RiskLevelEvaluator(
// High Risk: High Number of Notifications and Accurate Locations
// Medium Risk: Low Number of Notifications or only inaccurate Location Reports
- return if (numberOfNotifications >= NUMBER_OF_NOTIFICATIONS_FOR_HIGH_RISK && numberOfLocationsWithAccuracyLimit >= (device.deviceType?.getNumberOfLocationsToBeConsideredForTrackingDetection() ?: getLocationsAtLeastTrackedBeforeAlarm())) {
+ return if (numberOfNotifications >= NUMBER_OF_NOTIFICATIONS_FOR_HIGH_RISK && numberOfLocationsWithAccuracyLimit >= getNumberOfLocationsToBeConsideredForTrackingDetection(device.deviceType)) {
deviceRepository.updateRiskLevelCache(device.address, 2, LocalDateTime.now())
RiskLevel.HIGH
} else{
From da2042e3005040e4dfa7a67829b4d3621c4ca422 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 3 Jan 2024 16:02:58 +0100
Subject: [PATCH 044/154] separate the connected / low Risk devices from the
offline / high risk devices in manual scan. Remove settings to toggle between
scan type
---
.../notifications/NotificationService.kt | 2 +-
.../ui/scan/BluetoothDeviceAdapter.kt | 2 +-
.../ui/scan/ScanFragment.kt | 30 +++++++---
.../ui/scan/ScanViewModel.kt | 56 ++++++++++++-------
.../at_tracking_detection/util/SharedPrefs.kt | 34 +++++------
app/src/main/res/layout/fragment_scan.xml | 28 ++++++++--
app/src/main/res/xml/fragment_settings.xml | 7 ---
7 files changed, 95 insertions(+), 64 deletions(-)
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 d9749dc0..91c01e9c 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
@@ -146,7 +146,7 @@ class NotificationService @Inject constructor(
// Do not send multiple notifications
if (!ATTrackingDetectionApplication.SURVEY_IS_RUNNING) {return}
- if (SharedPrefs.surveyNotficationSent && !replace) {return}
+ if (SharedPrefs.surveyNotificationSent && !replace) {return}
//Check if already scheduled
val notificationDate = SharedPrefs.surveyNotificationDate
if ( replace || notificationDate == null || notificationDate < LocalDateTime.now()) {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
index ac183bde..a8f5e7cd 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
@@ -18,7 +18,7 @@ import de.seemoo.at_tracking_detection.ui.scan.dialog.PlaySoundDialogFragment
import de.seemoo.at_tracking_detection.util.Utility
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
-class BluetoothDeviceAdapter constructor(private val fragmentManager: FragmentManager) :
+class BluetoothDeviceAdapter(private val fragmentManager: FragmentManager) :
ListAdapter(Companion) {
class ScanResultViewHolder(private val binding: ItemScanResultBinding) :
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
index 424d65e1..ef944dde 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
@@ -33,16 +33,25 @@ class ScanFragment : Fragment() {
): View {
val binding: FragmentScanBinding =
DataBindingUtil.inflate(inflater, R.layout.fragment_scan, container, false)
- val bluetoothDeviceAdapter = BluetoothDeviceAdapter(childFragmentManager)
+ val bluetoothDeviceAdapterHighRisk = BluetoothDeviceAdapter(childFragmentManager)
+ val bluetoothDeviceAdapterLowRisk = BluetoothDeviceAdapter(childFragmentManager)
- binding.adapter = bluetoothDeviceAdapter
+ binding.adapterHighRisk = bluetoothDeviceAdapterHighRisk
+ binding.adapterLowRisk = bluetoothDeviceAdapterLowRisk
binding.lifecycleOwner = viewLifecycleOwner
binding.vm = scanViewModel
- scanViewModel.bluetoothDeviceList.observe(viewLifecycleOwner) {
- bluetoothDeviceAdapter.submitList(it)
+ // TODO: possible to optimize this???
+ scanViewModel.bluetoothDeviceListHighRisk.observe(viewLifecycleOwner) {
+ bluetoothDeviceAdapterHighRisk.submitList(it)
// Ugly workaround because i don't know why this adapter only displays items after a screen wake up...
- bluetoothDeviceAdapter.notifyDataSetChanged()
+ bluetoothDeviceAdapterHighRisk.notifyDataSetChanged()
+ }
+
+ scanViewModel.bluetoothDeviceListLowRisk.observe(viewLifecycleOwner) {
+ bluetoothDeviceAdapterLowRisk.submitList(it)
+ // Ugly workaround because i don't know why this adapter only displays items after a screen wake up...
+ bluetoothDeviceAdapterLowRisk.notifyDataSetChanged()
}
scanViewModel.scanFinished.observe(viewLifecycleOwner) {
@@ -54,9 +63,14 @@ class ScanFragment : Fragment() {
}
scanViewModel.sortingOrder.observe(viewLifecycleOwner) {
- val bluetoothDeviceListValue = scanViewModel.bluetoothDeviceList.value ?: return@observe
- scanViewModel.sortResults(bluetoothDeviceListValue)
- scanViewModel.bluetoothDeviceList.postValue(bluetoothDeviceListValue)
+ val bluetoothDeviceListHighRiskValue = scanViewModel.bluetoothDeviceListHighRisk.value ?: return@observe
+ val bluetoothDeviceListLowRiskValue = scanViewModel.bluetoothDeviceListLowRisk.value ?: return@observe
+
+ scanViewModel.sortResults(bluetoothDeviceListHighRiskValue)
+ scanViewModel.sortResults(bluetoothDeviceListLowRiskValue)
+
+ scanViewModel.bluetoothDeviceListHighRisk.postValue(bluetoothDeviceListHighRiskValue)
+ scanViewModel.bluetoothDeviceListLowRisk.postValue(bluetoothDeviceListLowRiskValue)
if (view != null) {
val sortBySignalStrength = requireView().findViewById(R.id.sort_option_signal_strength)
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 c676fbc9..9823b73d 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
@@ -5,13 +5,13 @@ import android.widget.TextView
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.map
import dagger.hilt.android.lifecycle.HiltViewModel
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
-import de.seemoo.at_tracking_detection.database.models.device.DeviceManager
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
+import de.seemoo.at_tracking_detection.database.models.device.DeviceManager
import de.seemoo.at_tracking_detection.database.models.device.DeviceManager.getDeviceType
import de.seemoo.at_tracking_detection.database.models.device.DeviceType.Companion.getAllowedDeviceTypesFromSettings
import de.seemoo.at_tracking_detection.database.repository.BeaconRepository
@@ -19,7 +19,6 @@ import de.seemoo.at_tracking_detection.database.repository.ScanRepository
import de.seemoo.at_tracking_detection.detection.LocationProvider
import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker
import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker.Companion.TIME_BETWEEN_BEACONS
-import de.seemoo.at_tracking_detection.util.SharedPrefs
import de.seemoo.at_tracking_detection.util.Utility
import de.seemoo.at_tracking_detection.util.ble.BLEScanner
import kotlinx.coroutines.MainScope
@@ -35,7 +34,9 @@ class ScanViewModel @Inject constructor(
private val locationProvider: LocationProvider,
) : ViewModel() {
- val bluetoothDeviceList = MutableLiveData>()
+ val bluetoothDeviceListHighRisk = MutableLiveData>()
+
+ val bluetoothDeviceListLowRisk = MutableLiveData>()
val scanFinished = MutableLiveData(false)
@@ -45,7 +46,8 @@ class ScanViewModel @Inject constructor(
var bluetoothEnabled = MutableLiveData(true)
init {
- bluetoothDeviceList.value = ArrayList()
+ bluetoothDeviceListHighRisk.value = ArrayList()
+ bluetoothDeviceListLowRisk.value = ArrayList()
bluetoothEnabled.value = BLEScanner.isBluetoothOn()
}
@@ -88,33 +90,37 @@ class ScanViewModel @Inject constructor(
}
}
- val bluetoothDeviceListValue = bluetoothDeviceList.value ?: return
- bluetoothDeviceListValue.removeIf {
+ val bluetoothDeviceListHighRiskValue = bluetoothDeviceListHighRisk.value ?: return
+ val bluetoothDeviceListLowRiskValue = bluetoothDeviceListLowRisk.value ?: return
+
+ bluetoothDeviceListHighRiskValue.removeIf {
getPublicKey(it) == uniqueIdentifier
}
-
- if (SharedPrefs.showConnectedDevices || BaseDevice.getConnectionState(scanResult) in DeviceManager.savedConnectionStates) {
- // only add possible devices to list
- bluetoothDeviceListValue.add(scanResult)
+ bluetoothDeviceListLowRiskValue.removeIf {
+ getPublicKey(it) == uniqueIdentifier
}
- if (!SharedPrefs.showConnectedDevices){
- // Do not show connected devices when criteria is met
- bluetoothDeviceListValue.removeIf {
- BaseDevice.getConnectionState(it) !in DeviceManager.savedConnectionStates
- }
+ if (BaseDevice.getConnectionState(scanResult) in DeviceManager.savedConnectionStates) {
+ // only add possible devices to list
+ bluetoothDeviceListHighRiskValue.add(scanResult)
+ } else {
+ bluetoothDeviceListLowRiskValue.add(scanResult)
}
- sortResults(bluetoothDeviceListValue)
+ // TODO: is sorting necessary long term?
+ sortResults(bluetoothDeviceListHighRiskValue)
+ sortResults(bluetoothDeviceListLowRiskValue)
- bluetoothDeviceList.postValue(bluetoothDeviceListValue)
+ bluetoothDeviceListHighRisk.postValue(bluetoothDeviceListHighRiskValue)
+ bluetoothDeviceListLowRisk.postValue(bluetoothDeviceListLowRiskValue)
Timber.d("Adding scan result ${scanResult.device.address} with unique identifier $uniqueIdentifier")
Timber.d(
"status bytes: ${
scanResult.scanRecord?.manufacturerSpecificData?.get(76)?.get(2)?.toString(2)
}"
)
- Timber.d("Device list: ${bluetoothDeviceList.value?.count()}")
+ Timber.d("Device list (High Risk): ${bluetoothDeviceListHighRisk.value?.count()}")
+ Timber.d("Device list (Low Risk): ${bluetoothDeviceListLowRisk.value?.count()}")
}
fun sortResults(bluetoothDeviceListValue: MutableList) {
@@ -142,7 +148,15 @@ class ScanViewModel @Inject constructor(
}
}
- val isListEmpty: LiveData = bluetoothDeviceList.map { it.isEmpty() }
+ val isListEmpty: LiveData = MediatorLiveData().apply {
+ // Function to update the isListEmpty LiveData
+ fun update() {
+ val highRiskEmpty = bluetoothDeviceListHighRisk.value?.isEmpty() ?: true
+ val lowRiskEmpty = bluetoothDeviceListLowRisk.value?.isEmpty() ?: true
+ this.value = highRiskEmpty && lowRiskEmpty
+ }
- val listSize: LiveData = bluetoothDeviceList.map { it.size }
+ addSource(bluetoothDeviceListHighRisk) { update() }
+ addSource(bluetoothDeviceListLowRisk) { update() }
+ }
}
\ 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 0dfad47a..8bfc79bf 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
@@ -32,10 +32,10 @@ object SharedPrefs {
get() {
val dateString = sharedPreferences.getString("last_scan", null)
if (dateString != null) {
- try {
- return LocalDateTime.parse(dateString)
+ return try {
+ LocalDateTime.parse(dateString)
}catch(e: DateTimeParseException) {
- return null
+ null
}
}
return null
@@ -64,10 +64,10 @@ object SharedPrefs {
get() {
val dateString = sharedPreferences.getString("lastDataDonation", null)
if (dateString != null) {
- try {
- return LocalDateTime.parse(dateString)
+ return try {
+ LocalDateTime.parse(dateString)
}catch(e: DateTimeParseException) {
- return null
+ null
}
}
return null
@@ -104,10 +104,10 @@ object SharedPrefs {
get() {
val dateString = sharedPreferences.getString("last_time_opened", null)
if (dateString != null) {
- try {
- return LocalDateTime.parse(dateString)
+ return try {
+ LocalDateTime.parse(dateString)
}catch(e: DateTimeParseException) {
- return null
+ null
}
}
return null
@@ -139,10 +139,10 @@ object SharedPrefs {
get() {
val dateString = sharedPreferences.getString("survey_notification_date", null)
if (dateString != null) {
- try {
- return LocalDateTime.parse(dateString)
+ return try {
+ LocalDateTime.parse(dateString)
}catch(e: DateTimeParseException) {
- return null
+ null
}
}
return null
@@ -151,21 +151,13 @@ object SharedPrefs {
sharedPreferences.edit().putString("survey_notification_date", value?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)).apply()
}
- var surveyNotficationSent: Boolean
+ var surveyNotificationSent: Boolean
get() {
return sharedPreferences.getBoolean("survey_notification_sent", false)
}set(value) {
sharedPreferences.edit().putBoolean("survey_notification_sent", value).apply()
}
- var showConnectedDevices: Boolean
- get() {
- return sharedPreferences.getBoolean("show_connected_devices", false)
- }
- set(value) {
- sharedPreferences.edit().putBoolean("show_connected_devices", value).apply()
- }
-
var riskSensitivity: String
// 0: Low
// 1: Medium
diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml
index 05e7570f..3f10fd47 100644
--- a/app/src/main/res/layout/fragment_scan.xml
+++ b/app/src/main/res/layout/fragment_scan.xml
@@ -10,7 +10,11 @@
type="de.seemoo.at_tracking_detection.ui.scan.ScanViewModel" />
+
+
@@ -108,22 +112,36 @@
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="@{!vm.scanFinished ? View.VISIBLE : View.GONE, default=visible}"
- app:layout_constraintBottom_toTopOf="@+id/scan_result_recycler"
+ app:layout_constraintBottom_toTopOf="@+id/scan_result_recycler_high_risk"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/sorting_options" />
+ app:setAdapter="@{adapter_high_risk}" >
+
+
+
+
diff --git a/app/src/main/res/xml/fragment_settings.xml b/app/src/main/res/xml/fragment_settings.xml
index d0667c55..7d4eea26 100644
--- a/app/src/main/res/xml/fragment_settings.xml
+++ b/app/src/main/res/xml/fragment_settings.xml
@@ -49,13 +49,6 @@
android:defaultValue="@array/devicesFilterValue"
/>
-
-
Date: Wed, 3 Jan 2024 18:16:27 +0100
Subject: [PATCH 045/154] separated ScanView is now a NestedScrollView
---
.../ui/scan/ScanViewModel.kt | 1 -
app/src/main/res/layout/fragment_scan.xml | 73 +++++++++++++------
app/src/main/res/values-de/strings.xml | 3 +
app/src/main/res/values/strings.xml | 2 +
4 files changed, 56 insertions(+), 23 deletions(-)
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 9823b73d..0b9abd4a 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
@@ -75,7 +75,6 @@ class ScanViewModel @Inject constructor(
since = currentDate.minusMinutes(TIME_BETWEEN_BEACONS)
) == 0) {
// There was no beacon with the address saved in the last IME_BETWEEN_BEACONS minutes
-
val location = locationProvider.getLastLocation() // if not working: checkRequirements = false
Timber.d("Got location $location in ScanViewModel")
diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml
index 3f10fd47..b89d5b2e 100644
--- a/app/src/main/res/layout/fragment_scan.xml
+++ b/app/src/main/res/layout/fragment_scan.xml
@@ -112,38 +112,67 @@
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="@{!vm.scanFinished ? View.VISIBLE : View.GONE, default=visible}"
- app:layout_constraintBottom_toTopOf="@+id/scan_result_recycler_high_risk"
+ app:layout_constraintBottom_toTopOf="@id/scan_result_wrapper"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/sorting_options" />
-
+ app:layout_constraintBottom_toBottomOf="parent">
-
+
-
+
+
+
+
+
+
+
+
+
-
+
+
Entwickler: %s
Maintainer: %s
+ Mögliche Tracker
+ Sichere Tracker
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c18f7084..2bccb2b6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -356,4 +356,6 @@
Copyright
Developer: %s
Maintainer: %s
+ Potential Trackers
+ Safe Trackers
From 0ebc53d49d4be88744a6cdc9be34ef1fbf77fc1f Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 7 Jan 2024 15:54:49 +0100
Subject: [PATCH 046/154] Manual Scan Behaviour now resembles the one from the
iOS App
---
.../ui/scan/BluetoothDeviceAdapter.kt | 29 +------
.../ui/scan/ScanFragment.kt | 4 +-
.../util/BindingAdapter.kt | 39 ---------
app/src/main/res/layout/fragment_scan.xml | 13 +--
app/src/main/res/layout/item_device.xml | 2 +-
app/src/main/res/layout/item_scan_result.xml | 85 +++++--------------
6 files changed, 26 insertions(+), 146 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
index a8f5e7cd..11a47fa7 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
@@ -1,24 +1,18 @@
package de.seemoo.at_tracking_detection.ui.scan
-import android.Manifest
import android.bluetooth.le.ScanResult
-import android.os.Build
import android.view.LayoutInflater
import android.view.ViewGroup
-import android.widget.ImageView
-import androidx.fragment.app.FragmentManager
import androidx.navigation.findNavController
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.card.MaterialCardView
import de.seemoo.at_tracking_detection.R
-import de.seemoo.at_tracking_detection.databinding.ItemScanResultBinding
-import de.seemoo.at_tracking_detection.ui.scan.dialog.PlaySoundDialogFragment
-import de.seemoo.at_tracking_detection.util.Utility
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
+import de.seemoo.at_tracking_detection.databinding.ItemScanResultBinding
-class BluetoothDeviceAdapter(private val fragmentManager: FragmentManager) :
+class BluetoothDeviceAdapter:
ListAdapter(Companion) {
class ScanResultViewHolder(private val binding: ItemScanResultBinding) :
@@ -48,25 +42,6 @@ class BluetoothDeviceAdapter(private val fragmentManager: FragmentManager) :
holder.itemView.findNavController()
.navigate(directions)
}
-
- holder.itemView.findViewById(R.id.scan_signal_strength)
- .setOnClickListener() {
- val deviceAddress: String = getPublicKey(scanResult)
- val directions = ScanFragmentDirections.actionScanToScanDistance(deviceAddress)
- holder.itemView.findNavController()
- .navigate(directions)
- }
-
- holder.itemView.findViewById(R.id.scan_result_play_sound)
- .setOnClickListener() {
- val hasAllPermissions =
- Build.VERSION.SDK_INT < Build.VERSION_CODES.S || Utility.checkAndRequestPermission(
- Manifest.permission.BLUETOOTH_CONNECT
- )
- if (hasAllPermissions) {
- PlaySoundDialogFragment(scanResult).show(fragmentManager, null)
- }
- }
}
companion object : DiffUtil.ItemCallback() {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
index ef944dde..dd93e75c 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
@@ -33,8 +33,8 @@ class ScanFragment : Fragment() {
): View {
val binding: FragmentScanBinding =
DataBindingUtil.inflate(inflater, R.layout.fragment_scan, container, false)
- val bluetoothDeviceAdapterHighRisk = BluetoothDeviceAdapter(childFragmentManager)
- val bluetoothDeviceAdapterLowRisk = BluetoothDeviceAdapter(childFragmentManager)
+ val bluetoothDeviceAdapterHighRisk = BluetoothDeviceAdapter()
+ val bluetoothDeviceAdapterLowRisk = BluetoothDeviceAdapter()
binding.adapterHighRisk = bluetoothDeviceAdapterHighRisk
binding.adapterLowRisk = bluetoothDeviceAdapterLowRisk
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
index dfaa5edd..2142eede 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
@@ -2,16 +2,13 @@ package de.seemoo.at_tracking_detection.util
import android.annotation.SuppressLint
import android.bluetooth.le.ScanResult
-import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.card.MaterialCardView
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
-import de.seemoo.at_tracking_detection.database.models.device.ConnectionState
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
import java.util.*
@@ -44,17 +41,6 @@ fun setDeviceDrawable(imageView: ImageView, scanResult: ScanResult) {
imageView.setImageDrawable(device.getDrawable())
}
-@BindingAdapter("setDeviceColor", requireAll = true)
-fun setDeviceColor(materialCardView: MaterialCardView, scanResult: ScanResult) {
- when (BaseDevice.getConnectionState(scanResult)) {
- ConnectionState.CONNECTED -> materialCardView.setCardBackgroundColor(-7829368)
- ConnectionState.PREMATURE_OFFLINE -> materialCardView.setCardBackgroundColor(-7829368)
- ConnectionState.OFFLINE -> materialCardView.setCardBackgroundColor(-7829368)
- ConnectionState.OVERMATURE_OFFLINE -> materialCardView.setCardBackgroundColor(0)
- ConnectionState.UNKNOWN -> materialCardView.setCardBackgroundColor(0)
- }
-}
-
@BindingAdapter("setDeviceName", requireAll = true)
fun setDeviceName (textView: TextView, scanResult: ScanResult) {
val deviceRepository = ATTrackingDetectionApplication.getCurrentApp()?.deviceRepository
@@ -67,28 +53,3 @@ fun setDeviceName (textView: TextView, scanResult: ScanResult) {
}
}
-@BindingAdapter("hideWhenNoSoundPlayed", requireAll = true)
-fun hideWhenNoSoundPlayed(view: View, scanResult: ScanResult) {
- val device = BaseDevice(scanResult).device
- if (device.isConnectable() && BaseDevice.getConnectionState(scanResult) == ConnectionState.OVERMATURE_OFFLINE) {
- view.visibility = View.VISIBLE
- }else {
- view.visibility = View.INVISIBLE
- }
-}
-
-@BindingAdapter("visibilityBellIcon", requireAll = true)
-fun visibilityBellIcon(view: View, scanResult: ScanResult) {
- val deviceAddress = BaseDevice(scanResult).address
- val notificationRepository = ATTrackingDetectionApplication.getCurrentApp()?.notificationRepository
-
- if (notificationRepository != null) {
- if (notificationRepository.existsNotificationForDevice(deviceAddress)) {
- view.visibility = View.VISIBLE
- } else {
- view.visibility = View.INVISIBLE
- }
- } else {
- view.visibility = View.INVISIBLE
- }
-}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml
index b89d5b2e..382b6617 100644
--- a/app/src/main/res/layout/fragment_scan.xml
+++ b/app/src/main/res/layout/fragment_scan.xml
@@ -29,7 +29,7 @@
android:id="@+id/sorting_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:visibility="@{vm.isListEmpty ? View.GONE : View.VISIBLE, default=visible}"
+ android:visibility="@{!vm.bluetoothEnabled || (vm.scanFinished && vm.isListEmpty) ? View.GONE : View.VISIBLE, default=visible}"
app:layout_constraintBottom_toTopOf="@id/scan_result_loading_bar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
@@ -121,6 +121,7 @@
android:id="@+id/scan_result_wrapper"
android:layout_width="match_parent"
android:layout_height="0dp"
+ android:visibility="@{!vm.bluetoothEnabled || (vm.scanFinished && vm.isListEmpty) ? View.GONE : View.VISIBLE, default=visible}"
app:layout_constraintTop_toBottomOf="@id/scan_result_loading_bar"
app:layout_constraintBottom_toBottomOf="parent">
@@ -174,16 +175,6 @@
-
-
-
+ android:layout_height="60dp"
+ android:layout_margin="@dimen/card_margin">
+ android:layout_margin="4dp">
-
-
-
-
+ app:layout_constraintGuide_percent="0.8" />
@@ -73,32 +58,19 @@
android:id="@+id/scan_result_device_name"
android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_marginStart="6dp"
- android:autoSizeTextType="uniform"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp"
android:maxLines="1"
+ android:textSize="16sp"
+ android:gravity="center_vertical"
android:textAppearance="?attr/textAppearanceTitleLarge"
- app:layout_constraintBottom_toTopOf="@id/scan_result_guideline_horizontal"
- app:layout_constraintLeft_toRightOf="@id/scan_result_guideline_left"
- app:layout_constraintRight_toLeftOf="@id/scan_result_guideline_middle"
+ app:layout_constraintLeft_toLeftOf="@id/scan_result_guideline_left"
+ app:layout_constraintRight_toLeftOf="@id/scan_result_guideline_right"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:setDeviceName="@{scanResult}"
tools:text="Debug Device" />
-
-
-
-
+ app:layout_constraintRight_toRightOf="parent"
+ app:srcCompat="@drawable/ic_baseline_chevron_right_24"
+ tools:ignore="ContentDescription" />
From 3a54b662b086d7daf5a9d9200682f17bfe879c1e Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 8 Jan 2024 15:12:36 +0100
Subject: [PATCH 047/154] replace some of the deprecated Java Functions with
backwards compatible replacements
---
.../hilt/DatabaseModule.kt | 40 ++++++-------
.../ScheduledNotificationReceiver.kt | 2 +-
.../ui/OnboardingActivity.kt | 2 +-
.../ui/scan/ScanDistanceFragment.kt | 20 ++++---
.../ui/scan/dialog/PlaySoundDialogFragment.kt | 57 +++++++++++--------
.../ui/tracking/ObserveTrackerFragment.kt | 24 ++++----
6 files changed, 79 insertions(+), 66 deletions(-)
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 a5f0d8ef..5aaff441 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
@@ -23,9 +23,9 @@ import javax.inject.Singleton
object DatabaseModule {
val MIGRATION_5_7 = object : Migration(5, 7) {
- override fun migrate(database: SupportSQLiteDatabase) {
+ override fun migrate(db: SupportSQLiteDatabase) {
try {
- database.execSQL("ALTER TABLE `beacon` ADD COLUMN `serviceUUIDs` TEXT DEFAULT NULL")
+ db.execSQL("ALTER TABLE `beacon` ADD COLUMN `serviceUUIDs` TEXT DEFAULT NULL")
}catch (e: SQLiteException) {
Timber.e("Could not create new column ${e}")
}
@@ -34,17 +34,17 @@ object DatabaseModule {
}
val MIGRATION_6_7 = object : Migration(6, 7) {
- override fun migrate(database: SupportSQLiteDatabase) {
+ override fun migrate(db: SupportSQLiteDatabase) {
}
}
val MIGRATION_9_10 = object : Migration(9, 10) {
- override fun migrate(database: SupportSQLiteDatabase) {
+ override fun migrate(db: SupportSQLiteDatabase) {
// add location table and locationID to beacon
try {
- database.execSQL("CREATE TABLE `location` (`locationId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `firstDiscovery` TEXT NOT NULL, `lastSeen` TEXT NOT NULL, `longitude` REAL NOT NULL, `latitude` REAL NOT NULL, `accuracy` REAL)")
- database.execSQL("CREATE UNIQUE INDEX `index_location_latitude_longitude` ON `location` (`latitude`, `longitude`)")
- database.execSQL("ALTER TABLE `beacon` ADD COLUMN `locationId` INTEGER")
+ db.execSQL("CREATE TABLE `location` (`locationId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `firstDiscovery` TEXT NOT NULL, `lastSeen` TEXT NOT NULL, `longitude` REAL NOT NULL, `latitude` REAL NOT NULL, `accuracy` REAL)")
+ db.execSQL("CREATE UNIQUE INDEX `index_location_latitude_longitude` ON `location` (`latitude`, `longitude`)")
+ db.execSQL("ALTER TABLE `beacon` ADD COLUMN `locationId` INTEGER")
}catch (e: SQLiteException) {
Timber.e("Could not create location ${e}")
}
@@ -52,7 +52,7 @@ object DatabaseModule {
var sql: String
while (true) {
sql = "SELECT * FROM `beacon` WHERE `locationId` IS NULL AND `latitude` IS NOT NULL AND `longitude` IS NOT NULL LIMIT 1"
- val beacon = database.query(sql)
+ val beacon = db.query(sql)
if (beacon.count == 0) {
// If there are no more locations left to do, then break
@@ -68,7 +68,7 @@ object DatabaseModule {
// println("Longitude: $longitude")
sql = "SELECT `longitude`, `latitude` FROM `location` ORDER BY ABS(`latitude` - $latitude) + ABS(`longitude` - $longitude) ASC LIMIT 1"
- val closestLocation = database.query(sql)
+ val closestLocation = db.query(sql)
var insertNewLocation = false
@@ -103,7 +103,7 @@ object DatabaseModule {
var lastSeen = firstDiscovery // receivedAt
sql = "SELECT `firstDiscovery`, `lastSeen` FROM `device` WHERE `address` = '$deviceAddress'"
- val device = database.query(sql)
+ val device = db.query(sql)
if (device.count > 0) {
// println("Successfully got timestamps from device table")
@@ -113,11 +113,11 @@ object DatabaseModule {
}
sql = "INSERT INTO `location` (`firstDiscovery`, `lastSeen`, `longitude`, `latitude`) VALUES ('$firstDiscovery', '$lastSeen', $longitude, $latitude)"
- database.execSQL(sql)
+ db.execSQL(sql)
}
sql = "SELECT `locationId` FROM `location` WHERE `latitude` = $latitude AND `longitude` = $longitude"
- val location = database.query(sql)
+ val location = db.query(sql)
println(location.count)
if (location.count > 0) { // else: locationId stays null
location.moveToFirst()
@@ -126,25 +126,25 @@ object DatabaseModule {
val beaconId = beacon.getInt(0)
println("beaconId: $beaconId")
sql = "UPDATE `beacon` SET `locationId` = $locationId WHERE `locationId` IS NULL AND `beaconId` = $beaconId"
- database.execSQL(sql)
+ db.execSQL(sql)
sql = "SELECT * FROM `beacon` WHERE `locationId` IS NOT NULL"
- println(database.query(sql).count)
+ println(db.query(sql).count)
}
}
try {
- database.execSQL("CREATE TABLE `beacon_backup` (`beaconId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `receivedAt` TEXT NOT NULL, `rssi` INTEGER NOT NULL, `deviceAddress` TEXT NOT NULL, `locationId` INTEGER, `mfg` BLOB, `serviceUUIDs` TEXT)")
- database.execSQL("INSERT INTO `beacon_backup` SELECT `beaconId`, `receivedAt`, `rssi`, `deviceAddress`, `locationId`, `mfg`, `serviceUUIDs` FROM `beacon`")
- database.execSQL("DROP TABLE `beacon`")
+ db.execSQL("CREATE TABLE `beacon_backup` (`beaconId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `receivedAt` TEXT NOT NULL, `rssi` INTEGER NOT NULL, `deviceAddress` TEXT NOT NULL, `locationId` INTEGER, `mfg` BLOB, `serviceUUIDs` TEXT)")
+ db.execSQL("INSERT INTO `beacon_backup` SELECT `beaconId`, `receivedAt`, `rssi`, `deviceAddress`, `locationId`, `mfg`, `serviceUUIDs` FROM `beacon`")
+ db.execSQL("DROP TABLE `beacon`")
} catch (e: SQLiteException) {
Timber.e("Could not create beacon_backup ${e}")
}
try {
- database.execSQL("CREATE TABLE `beacon` (`beaconId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `receivedAt` TEXT NOT NULL, `rssi` INTEGER NOT NULL, `deviceAddress` TEXT NOT NULL, `locationId` INTEGER, `mfg` BLOB, `serviceUUIDs` TEXT)")
- database.execSQL("INSERT INTO `beacon` SELECT `beaconId`, `receivedAt`, `rssi`, `deviceAddress`, `locationId`, `mfg`, `serviceUUIDs` FROM `beacon_backup`")
- database.execSQL("DROP TABLE `beacon_backup`")
+ db.execSQL("CREATE TABLE `beacon` (`beaconId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `receivedAt` TEXT NOT NULL, `rssi` INTEGER NOT NULL, `deviceAddress` TEXT NOT NULL, `locationId` INTEGER, `mfg` BLOB, `serviceUUIDs` TEXT)")
+ db.execSQL("INSERT INTO `beacon` SELECT `beaconId`, `receivedAt`, `rssi`, `deviceAddress`, `locationId`, `mfg`, `serviceUUIDs` FROM `beacon_backup`")
+ db.execSQL("DROP TABLE `beacon_backup`")
} catch (e: SQLiteException) {
Timber.e("Could not create beacon ${e}")
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/notifications/ScheduledNotificationReceiver.kt b/app/src/main/java/de/seemoo/at_tracking_detection/notifications/ScheduledNotificationReceiver.kt
index c3c2cd36..7be05afc 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/notifications/ScheduledNotificationReceiver.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/notifications/ScheduledNotificationReceiver.kt
@@ -11,7 +11,7 @@ class ScheduledNotificationReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Timber.d("Broadcast received ${intent?.action}")
- val notificationService = ATTrackingDetectionApplication.getCurrentApp()?.notificationService
+ // val notificationService = ATTrackingDetectionApplication.getCurrentApp()?.notificationService
SharedPrefs.dismissSurveyInformation = true
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/OnboardingActivity.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/OnboardingActivity.kt
index c352ed1b..2d35eeb2 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/OnboardingActivity.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/OnboardingActivity.kt
@@ -77,7 +77,7 @@ class OnboardingActivity : AppIntro() {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
})
} else {
- onBackPressed()
+ finish()
}
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
index fde2a35a..7de47943 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
@@ -6,11 +6,13 @@ import android.bluetooth.le.ScanResult
import android.os.Bundle
import android.os.Handler
import android.os.Looper
+import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.animation.addListener
+import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -67,8 +69,8 @@ class ScanDistanceFragment : Fragment() {
viewModel.connectionQuality.postValue(displayedConnectionQuality)
val deviceType = DeviceManager.getDeviceType(it)
- setDeviceType(deviceType)
- setBattery(batteryState)
+ setDeviceType(requireContext(), deviceType)
+ setBattery(requireContext(), batteryState)
setHeight(connectionQuality)
if (viewModel.isFirstScanCallback.value as Boolean) {
@@ -153,19 +155,19 @@ class ScanDistanceFragment : Fragment() {
}
}
- private fun setBattery(batteryState: BatteryState) {
+ private fun setBattery(context: Context, batteryState: BatteryState) {
binding.batteryLayout.visibility = View.VISIBLE
when(batteryState) {
- BatteryState.FULL -> binding.batterySymbol.setImageDrawable(resources.getDrawable(R.drawable.ic_battery_full_24))
- BatteryState.MEDIUM -> binding.batterySymbol.setImageDrawable(resources.getDrawable(R.drawable.ic_battery_medium_24))
- BatteryState.LOW -> binding.batterySymbol.setImageDrawable(resources.getDrawable(R.drawable.ic_battery_low_24))
- BatteryState.VERY_LOW -> binding.batterySymbol.setImageDrawable(resources.getDrawable(R.drawable.ic_battery_very_low_24))
+ BatteryState.FULL -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_full_24))
+ BatteryState.MEDIUM -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_medium_24))
+ BatteryState.LOW -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_low_24))
+ BatteryState.VERY_LOW -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_very_low_24))
else -> binding.batteryLayout.visibility = View.GONE
}
}
- private fun setDeviceType(deviceType: DeviceType) {
- val drawable = resources.getDrawable(DeviceType.getImageDrawable(deviceType))
+ private fun setDeviceType(context: Context, deviceType: DeviceType) {
+ val drawable = ContextCompat.getDrawable(context, DeviceType.getImageDrawable(deviceType))
binding.deviceTypeSymbol.setImageDrawable(drawable)
binding.deviceTypeText.text = DeviceType.userReadableName(deviceType)
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/PlaySoundDialogFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/PlaySoundDialogFragment.kt
index bce24724..9bcc07b3 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/PlaySoundDialogFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/PlaySoundDialogFragment.kt
@@ -10,7 +10,9 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
@@ -20,6 +22,7 @@ import de.seemoo.at_tracking_detection.database.models.device.DeviceManager
import de.seemoo.at_tracking_detection.databinding.DialogPlaySoundBinding
import de.seemoo.at_tracking_detection.util.ble.BluetoothConstants
import de.seemoo.at_tracking_detection.util.ble.BluetoothLeService
+import kotlinx.coroutines.launch
import timber.log.Timber
class PlaySoundDialogFragment constructor(scanResult: ScanResult) : BottomSheetDialogFragment() {
@@ -49,30 +52,36 @@ class PlaySoundDialogFragment constructor(scanResult: ScanResult) : BottomSheetD
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- lifecycleScope.launchWhenStarted {
- viewModel.playSoundState.collect {
- when (it) {
- is DialogViewModel.ConnectionState.Playing -> {
- binding.spinnerConnecting.visibility = View.INVISIBLE
- binding.spinnerPlaying.visibility = View.VISIBLE
- }
- is DialogViewModel.ConnectionState.Connecting -> {
- binding.spinnerConnecting.visibility = View.VISIBLE
- }
- else -> {
- binding.spinnerConnecting.visibility = View.INVISIBLE
- binding.spinnerPlaying.visibility = View.INVISIBLE
- when (it) {
- is DialogViewModel.ConnectionState.Error -> {
- binding.imageError.visibility = View.VISIBLE
- binding.errorText.visibility = View.VISIBLE
- binding.errorText.text = it.message
- }
- is DialogViewModel.ConnectionState.Success -> {
- binding.imageSuccess.visibility = View.VISIBLE
- }
- else -> {
- Timber.d("Reached unknown state $it!")
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.playSoundState.collect {
+ when (it) {
+ is DialogViewModel.ConnectionState.Playing -> {
+ binding.spinnerConnecting.visibility = View.INVISIBLE
+ binding.spinnerPlaying.visibility = View.VISIBLE
+ }
+
+ is DialogViewModel.ConnectionState.Connecting -> {
+ binding.spinnerConnecting.visibility = View.VISIBLE
+ }
+
+ else -> {
+ binding.spinnerConnecting.visibility = View.INVISIBLE
+ binding.spinnerPlaying.visibility = View.INVISIBLE
+ when (it) {
+ is DialogViewModel.ConnectionState.Error -> {
+ binding.imageError.visibility = View.VISIBLE
+ binding.errorText.visibility = View.VISIBLE
+ binding.errorText.text = it.message
+ }
+
+ is DialogViewModel.ConnectionState.Success -> {
+ binding.imageSuccess.visibility = View.VISIBLE
+ }
+
+ else -> {
+ Timber.d("Reached unknown state $it!")
+ }
}
}
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerFragment.kt
index 89576d83..c374e514 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerFragment.kt
@@ -21,8 +21,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.time.LocalDateTime
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleObserver
-import androidx.lifecycle.OnLifecycleEvent
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
import de.seemoo.at_tracking_detection.worker.ScheduleWorkersReceiver
@@ -81,10 +81,11 @@ class ObserveTrackerFragment: Fragment() {
}
// Ensure coroutine cancellation if needed
- viewLifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {
- @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
- fun onDestroy() {
- coroutine.cancel()
+ viewLifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
+ override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+ if (event == Lifecycle.Event.ON_DESTROY) {
+ coroutine.cancel()
+ }
}
})
}
@@ -130,12 +131,13 @@ class ObserveTrackerFragment: Fragment() {
}
// Ensure coroutine cancellation if needed
- viewLifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {
- @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
- fun onDestroy() {
- coroutine.cancel()
+ viewLifecycleOwner.lifecycle.addObserver(
+ LifecycleEventObserver { _, event ->
+ if (event == Lifecycle.Event.ON_DESTROY) {
+ coroutine.cancel()
+ }
}
- })
+ )
}
}
From 238247e7476c7d730be9262f967163450d9caa2c Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 9 Jan 2024 11:47:44 +0100
Subject: [PATCH 048/154] upgrade gradle to 8.2.1
---
build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/build.gradle b/build.gradle
index 22e846d2..3aaf12a8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,7 +13,7 @@ buildscript {
}
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.2.0'
+ classpath 'com.android.tools.build:gradle:8.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0"
From 0ae5330a1b291fa5a436512d68646a748fbe784f Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 9 Jan 2024 11:53:27 +0100
Subject: [PATCH 049/154] Scanning behaviour of Tile Tracker now closer to iOS
Version
---
.../database/models/device/types/Chipolo.kt | 12 +++++++++++-
.../database/models/device/types/Tile.kt | 11 ++++++++++-
2 files changed, 21 insertions(+), 2 deletions(-)
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 9ff15105..07627ae6 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
@@ -38,7 +38,17 @@ class Chipolo(val id: Int) : Device() {
get() = 0u
override val numberOfDaysToBeConsideredForTrackingDetection: Long
- get() = 2
+ get() = 1
+
+ override val numberOfLocationsToBeConsideredForTrackingDetectionLow: Int
+ get() = super.numberOfLocationsToBeConsideredForTrackingDetectionLow
+
+ override val numberOfLocationsToBeConsideredForTrackingDetectionMedium: Int
+ get() = super.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+
+ override val numberOfLocationsToBeConsideredForTrackingDetectionHigh: Int
+ get() = super.numberOfLocationsToBeConsideredForTrackingDetectionHigh
+
val offlineFindingServiceUUID: ParcelUuid = ParcelUuid.fromString("0000FE33-0000-1000-8000-00805F9B34FB")
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 a12d35bf..29f98af4 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
@@ -39,7 +39,16 @@ class Tile(val id: Int) : Device(){
get() = 0u
override val numberOfDaysToBeConsideredForTrackingDetection: Long
- get() = 2
+ get() = 1
+
+ override val numberOfLocationsToBeConsideredForTrackingDetectionLow: Int
+ get() = super.numberOfLocationsToBeConsideredForTrackingDetectionLow
+
+ override val numberOfLocationsToBeConsideredForTrackingDetectionMedium: Int
+ get() = super.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+
+ override val numberOfLocationsToBeConsideredForTrackingDetectionHigh: Int
+ get() = super.numberOfLocationsToBeConsideredForTrackingDetectionHigh
val offlineFindingServiceUUID: ParcelUuid = ParcelUuid.fromString("0000FEED-0000-1000-8000-00805F9B34FB")
}
From 66dbcfbec01717af9036bf8f352d969040473c43 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 9 Jan 2024 13:11:52 +0100
Subject: [PATCH 050/154] Device Detail Page now vertical, similar to iOS
Design
---
app/src/main/res/layout/fragment_tracking.xml | 235 ++++++++----------
.../main/res/layout/include_tracking_tile.xml | 46 ++--
2 files changed, 114 insertions(+), 167 deletions(-)
diff --git a/app/src/main/res/layout/fragment_tracking.xml b/app/src/main/res/layout/fragment_tracking.xml
index 3254d32d..01f29aa3 100644
--- a/app/src/main/res/layout/fragment_tracking.xml
+++ b/app/src/main/res/layout/fragment_tracking.xml
@@ -95,153 +95,114 @@
app:deviceBeaconCount="@{vm.amountBeacons}"
app:isClickable="@{false}" />
-
+ android:layout_weight="1"
+ android:visibility="@{vm.deviceAddress == null ? View.GONE : View.VISIBLE}"
+ bind:clickable="@{true}"
+ bind:drawable="@{@drawable/ic_scan_icon}"
+ bind:imageDescription="@{@string/tracking_detail_scan_title}"
+ bind:subtitle="@{@string/tracking_detail_scan_subtitle}"
+ bind:title="@{@string/tracking_detail_scan_title}"
+ bind:vm="@{vm}" />
-
-
-
-
-
-
-
-
-
+ android:layout_height="0dp"
+ android:minHeight="@dimen/tracking_tile_height"
+ android:layout_margin="@dimen/card_margin"
+ android:layout_weight="1"
+ android:visibility="@{vm.noLocationsYet ? View.GONE : View.VISIBLE}"
+ bind:checkable="@{true}"
+ bind:checked="@{vm.trackerObserved}"
+ bind:drawable="@{@drawable/ic_clock}"
+ bind:clickable="@{true}"
+ bind:imageDescription="@{@string/observe_tracker_title}"
+ bind:subtitle="@{@string/tracking_observe_tracker_subtitle}"
+ bind:title="@{@string/observe_tracker_title}"
+ bind:vm="@{vm}"
+ />
-
-
-
-
-
-
+ android:layout_height="0dp"
+ android:minHeight="@dimen/tracking_tile_height"
+ android:layout_margin="@dimen/card_margin"
+ android:layout_weight="1"
+ android:onClick="@{() -> vm.toggleIgnoreDevice()}"
+ android:visibility="@{vm.canBeIgnored ? View.VISIBLE : View.GONE}"
+ bind:checkable="@{true}"
+ bind:checked="@{vm.deviceIgnored}"
+ bind:clickable="@{true}"
+ bind:drawable="@{@drawable/ic_baseline_alarm_off_24}"
+ bind:imageDescription="@{@string/tracking_ignore_device_title}"
+ bind:subtitle="@{@string/tracking_ignore_device_subtitle}"
+ bind:title="@{@string/tracking_ignore_device_title}"
+ bind:vm="@{vm}"
+ />
-
+ android:layout_height="0dp"
+ android:minHeight="@dimen/tracking_tile_height"
+ android:layout_margin="@dimen/card_margin"
+ android:layout_weight="1"
+ android:onClick="@{() -> vm.toggleFalseAlarm()}"
+ android:visibility="@{vm.notificationId == -1 ? View.GONE : View.VISIBLE}"
+ bind:checkable="@{true}"
+ bind:checked="@{vm.falseAlarm}"
+ bind:clickable="@{true}"
+ bind:drawable="@{@drawable/ic_baseline_error_outline_24}"
+ bind:imageDescription="@{@string/tracking_false_alarm_title}"
+ bind:subtitle="@{@string/tracking_false_alarm_subtitle}"
+ bind:title="@{@string/tracking_false_alarm_title}"
+ bind:vm="@{vm}"
+ />
-
+
-
-
+
diff --git a/app/src/main/res/layout/include_tracking_tile.xml b/app/src/main/res/layout/include_tracking_tile.xml
index 45e1afa1..11eab4a0 100644
--- a/app/src/main/res/layout/include_tracking_tile.xml
+++ b/app/src/main/res/layout/include_tracking_tile.xml
@@ -60,13 +60,6 @@
android:layout_height="match_parent"
android:layout_margin="8dp">
-
-
+ tools:src="@drawable/ic_baseline_play_circle_outline_24" />
+ app:srcCompat="@drawable/ic_baseline_error_outline_24" />
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/tracking_tile_image"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="Title text" />
+ app:layout_constraintTop_toBottomOf="@id/tracking_tile_title"
+ tools:text="Subtitle text" />
\ No newline at end of file
From b723cff1bcb338cb66fab3d7ef334173fbaa493a Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 9 Jan 2024 15:04:21 +0100
Subject: [PATCH 051/154] add Website Link to Tracker Detail Page
---
.../database/models/device/DeviceContext.kt | 3 ++
.../database/models/device/types/AirPods.kt | 3 ++
.../database/models/device/types/AirTag.kt | 3 ++
.../models/device/types/AppleDevice.kt | 3 ++
.../database/models/device/types/Chipolo.kt | 3 ++
.../database/models/device/types/FindMy.kt | 3 ++
.../models/device/types/SamsungDevice.kt | 3 ++
.../database/models/device/types/SmartTag.kt | 3 ++
.../models/device/types/SmartTagPlus.kt | 3 ++
.../database/models/device/types/Tile.kt | 3 ++
.../ui/tracking/TrackingFragment.kt | 6 ++++
.../ui/tracking/TrackingViewModel.kt | 17 +++++++++++
app/src/main/res/layout/fragment_tracking.xml | 30 +++++++++++++++++--
app/src/main/res/values-de/strings.xml | 4 +++
app/src/main/res/values/strings.xml | 4 +++
15 files changed, 89 insertions(+), 2 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
index 5121bef3..7c301d04 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
@@ -28,6 +28,9 @@ interface DeviceContext {
val numberOfLocationsToBeConsideredForTrackingDetectionHigh: Int
get() = RiskLevelEvaluator.NUMBER_OF_LOCATIONS_BEFORE_ALARM_HIGH
+
+ val websiteManufacturer: String
+ get() = "https://www.seemoo.tu-darmstadt.de/"
fun getConnectionState(scanResult: ScanResult): ConnectionState {
return ConnectionState.UNKNOWN
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 993fde09..7c024380 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
@@ -162,6 +162,9 @@ class AirPods(val id: Int) : Device(), Connectable {
override val deviceType: DeviceType
get() = DeviceType.AIRPODS
+ override val websiteManufacturer: String
+ get() = "https://www.apple.com/airpods/"
+
override val defaultDeviceName: String
get() = "AirPods"
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 c8a894bc..aa1a6505 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
@@ -142,6 +142,9 @@ class AirTag(val id: Int) : Device(), Connectable {
override val statusByteDeviceType: UInt
get() = 1u
+ override val websiteManufacturer: String
+ get() = "https://www.apple.com/airtag/"
+
override fun getBatteryState(scanResult: ScanResult): BatteryState {
val mfg: ByteArray? = scanResult.scanRecord?.getManufacturerSpecificData(0x4C)
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 afd7da41..7251ab81 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
@@ -48,6 +48,9 @@ class AppleDevice(val id: Int) : Device() {
override val statusByteDeviceType: UInt
get() = 0u
+ override val websiteManufacturer: String
+ get() = "https://www.apple.com/"
+
override fun getConnectionState(scanResult: ScanResult): ConnectionState {
val mfg: ByteArray? = scanResult.scanRecord?.getManufacturerSpecificData(0x4C)
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 07627ae6..76a03046 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
@@ -37,6 +37,9 @@ class Chipolo(val id: Int) : Device() {
override val statusByteDeviceType: UInt
get() = 0u
+ override val websiteManufacturer: String
+ get() = "https://chipolo.net/"
+
override val numberOfDaysToBeConsideredForTrackingDetection: Long
get() = 1
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/FindMy.kt
index c45804ea..e4b0576c 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/FindMy.kt
@@ -179,6 +179,9 @@ class FindMy(val id: Int) : Device(), Connectable {
override val deviceType: DeviceType
get() = DeviceType.FIND_MY
+ override val websiteManufacturer: String
+ get() = "https://www.apple.com/"
+
override val defaultDeviceName: String
get() = "FindMy Device"
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SamsungDevice.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SamsungDevice.kt
index 38435872..1fe6aefa 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SamsungDevice.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SamsungDevice.kt
@@ -49,6 +49,9 @@ open class SamsungDevice(open val id: Int) : Device(){
override val statusByteDeviceType: UInt
get() = 0u
+ override val websiteManufacturer: String
+ get() = "https://www.samsung.com/"
+
private val offlineFindingServiceUUID: ParcelUuid = ParcelUuid.fromString("0000FD5A-0000-1000-8000-00805F9B34FB")
override fun getConnectionState(scanResult: ScanResult): ConnectionState {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTag.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTag.kt
index 0c137e0b..03720ff8 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTag.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTag.kt
@@ -42,6 +42,9 @@ class SmartTag(override val id: Int) : SamsungDevice(id) {
override val deviceType: DeviceType
get() = DeviceType.GALAXY_SMART_TAG
+ override val websiteManufacturer: String
+ get() = "https://www.samsung.com/"
+
override val defaultDeviceName: String
get() = ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.smarttag_no_uwb)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTagPlus.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTagPlus.kt
index b3af1bf1..3b8a1208 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTagPlus.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SmartTagPlus.kt
@@ -48,6 +48,9 @@ class SmartTagPlus(override val id: Int) : SamsungDevice(id) {
override val statusByteDeviceType: UInt
get() = 0u
+ override val websiteManufacturer: String
+ get() = "https://www.samsung.com/"
+
private val offlineFindingServiceUUID: ParcelUuid = ParcelUuid.fromString("0000FD5A-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 29f98af4..9f2b181b 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
@@ -35,6 +35,9 @@ class Tile(val id: Int) : Device(){
override val defaultDeviceName: String
get() = "Tile"
+ override val websiteManufacturer: String
+ get() = "https://www.tile.com/"
+
override val statusByteDeviceType: UInt
get() = 0u
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 1faef9e9..000891bc 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -9,6 +9,7 @@ import android.transition.TransitionInflater
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.Button
import android.widget.ImageButton
import androidx.activity.addCallback
import androidx.cardview.widget.CardView
@@ -125,6 +126,11 @@ class TrackingFragment : Fragment() {
val trackingDetailButton: CardView = view.findViewById(R.id.tracking_detail_scan)
val observeTrackerButton: CardView = view.findViewById(R.id.tracking_observation)
val map: MapView = view.findViewById(R.id.map)
+ val includedLayout: View = view.findViewById(R.id.manufacturer_website)
+
+ includedLayout.setOnClickListener {
+ trackingViewModel.clickOnWebsite(requireContext())
+ }
feedbackButton.setOnClickListener {
val directions: NavDirections =
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
index ef34950f..427fdabd 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
@@ -1,6 +1,10 @@
package de.seemoo.at_tracking_detection.ui.tracking
+import android.content.Intent
+import android.net.Uri
+import android.widget.Toast
import androidx.lifecycle.*
+import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.Beacon
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.database.models.device.Connectable
@@ -9,6 +13,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.database.repository.NotificationRepository
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import timber.log.Timber
import java.time.LocalDateTime
import javax.inject.Inject
@@ -25,6 +30,8 @@ class TrackingViewModel @Inject constructor(
val noLocationsYet = MutableLiveData(true)
+ val manufacturerWebsiteUrl = MutableLiveData("https://www.apple.com/airtag/")
+
val error = MutableLiveData(false)
val falseAlarm = MutableLiveData(false)
@@ -68,6 +75,7 @@ class TrackingViewModel @Inject constructor(
noLocationsYet.postValue(false)
connectable.postValue(it.device is Connectable)
showNfcHint.postValue(it.deviceType == DeviceType.AIRTAG)
+ manufacturerWebsiteUrl.postValue(it.device.deviceContext.websiteManufacturer)
val deviceType = it.deviceType
if (deviceType != null) {
this.canBeIgnored.postValue(deviceType.canBeIgnored())
@@ -99,4 +107,13 @@ class TrackingViewModel @Inject constructor(
falseAlarm.postValue(newState)
}
}
+
+ fun clickOnWebsite(context: android.content.Context) {
+ if (manufacturerWebsiteUrl.value != null) {
+ Timber.d("Click on website: ${manufacturerWebsiteUrl.value}")
+ val webpage: Uri = Uri.parse(manufacturerWebsiteUrl.value)
+ val intent = Intent(Intent.ACTION_VIEW, webpage)
+ context.startActivity(intent)
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_tracking.xml b/app/src/main/res/layout/fragment_tracking.xml
index 01f29aa3..ae5ff8fa 100644
--- a/app/src/main/res/layout/fragment_tracking.xml
+++ b/app/src/main/res/layout/fragment_tracking.xml
@@ -206,7 +206,6 @@
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index bb154fe6..a7ca0607 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -104,6 +104,7 @@
Bitte entscheide dich, ob du an unserer Studie teilnimmst oder nicht.
"Find My"-Gerät %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.
Signale
+%d
@@ -301,6 +302,9 @@
Version %s
Kontaktiere uns
Credits
+ Hersteller Website
+ Öffnet die Webseite des Herstellers
+ Kein Browser gefunden
Datenlöschung anfragen
Wenn Sie möchten, dass wir alle Daten, die wir im Rahmen unserer Studie von Ihnen erhoben haben, löschen, klicken Sie bitte auf die Schaltfläche unten. Dadurch werden Sie auch von der weiteren Teilnahme an der Studie ausgeschlossen und es werden keine weiteren Daten von Ihnen erhoben. Wenn Sie unsere Forschung weiterhin unterstützen möchten, können Sie einfach den Onboarding-Dialog erneut durchlaufen und auf "Ja" bei der Studienteilnahme klicken.
Datenlöschung erfolgreich
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2bccb2b6..56ce7fee 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -341,6 +341,10 @@
SmartTag
SmartTag (with UWB)
+ Website Manufacturer
+ Open the Website of the manufacturer of the tracker
+ No Browser found
+
Request deletion of Data
If you would like us to delete all the data we have collected from you as part of our study, please click the button below. This will also opt you out of further participation in the study. If you would like to continue supporting our research at any point, you can simply reactivate the onboarding process and click yes to study participation.
Data deletion successful
From 86528682be2d6dda58bac4eb334d934119073052 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 9 Jan 2024 23:55:55 +0100
Subject: [PATCH 052/154] Update deprecated Code
---
.../ui/scan/BluetoothDeviceAdapter.kt | 4 ++--
.../at_tracking_detection/util/BindingAdapter.kt | 11 +++++------
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
index 11a47fa7..570cf7c7 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
@@ -46,10 +46,10 @@ class BluetoothDeviceAdapter:
companion object : DiffUtil.ItemCallback() {
override fun areContentsTheSame(oldItem: ScanResult, newItem: ScanResult): Boolean =
- oldItem == newItem
+ getPublicKey(oldItem) == getPublicKey(newItem)
override fun areItemsTheSame(oldItem: ScanResult, newItem: ScanResult): Boolean =
- getPublicKey(oldItem) == getPublicKey(newItem)
+ oldItem == newItem
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
index 2142eede..3a340a19 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
@@ -1,9 +1,9 @@
package de.seemoo.at_tracking_detection.util
-import android.annotation.SuppressLint
import android.bluetooth.le.ScanResult
import android.widget.ImageView
import android.widget.TextView
+import androidx.core.content.ContextCompat
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
@@ -20,17 +20,16 @@ fun RecyclerView.bindRecyclerViewAdapter(adapter: RecyclerView.Adapter<*>) {
}
}
-@SuppressLint("UseCompatLoadingForDrawables")
@BindingAdapter("setSignalStrengthDrawable", requireAll = true)
fun setSignalStrengthDrawable(imageView: ImageView, scanResult: ScanResult) {
val rssi: Int = scanResult.rssi
val quality = Utility.dbmToQuality(rssi)
when (quality) {
- 0 -> imageView.setImageDrawable(imageView.context.getDrawable(R.drawable.ic_signal_low))
- 1 -> imageView.setImageDrawable(imageView.context.getDrawable(R.drawable.ic_signal_middle_low))
- 2 -> imageView.setImageDrawable(imageView.context.getDrawable(R.drawable.ic_signal_middle_high))
- 3 -> imageView.setImageDrawable(imageView.context.getDrawable(R.drawable.ic_signal_high))
+ 0 -> imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, R.drawable.ic_signal_low))
+ 1 -> imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, R.drawable.ic_signal_middle_low))
+ 2 -> imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, R.drawable.ic_signal_middle_high))
+ 3 -> imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, R.drawable.ic_signal_high))
}
}
From 19b1eb880546bff9cb63833d21a9a8029c49f25d Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 15 Jan 2024 14:36:21 +0100
Subject: [PATCH 053/154] change relevant days to relevant hours
---
.../database/models/device/DeviceContext.kt | 4 ++--
.../database/models/device/DeviceType.kt | 22 +++++++++----------
.../database/models/device/types/Chipolo.kt | 4 ++--
.../database/models/device/types/Tile.kt | 4 ++--
.../ui/dashboard/RiskCardViewModel.kt | 7 +++---
.../ui/tracking/TrackingFragment.kt | 1 -
.../ui/tracking/TrackingViewModel.kt | 5 +----
.../util/risk/RiskLevelEvaluator.kt | 9 ++++----
app/src/main/res/values-de/strings.xml | 4 ++--
app/src/main/res/values/strings.xml | 4 ++--
10 files changed, 30 insertions(+), 34 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
index 7c301d04..81f3b007 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
@@ -17,8 +17,8 @@ interface DeviceContext {
val statusByteDeviceType: UInt
- val numberOfDaysToBeConsideredForTrackingDetection: Long
- get() = RiskLevelEvaluator.RELEVANT_DAYS
+ val numberOfHoursToBeConsideredForTrackingDetection: Long
+ get() = RiskLevelEvaluator.RELEVANT_HOURS
val numberOfLocationsToBeConsideredForTrackingDetectionLow: Int
get() = RiskLevelEvaluator.NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW
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 ed83385d..639882d4 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
@@ -81,18 +81,18 @@ enum class DeviceType {
}
}
- fun getNumberOfDaysToBeConsideredForTrackingDetection(): Long {
+ fun getNumberOfHoursToBeConsideredForTrackingDetection(): Long {
return when (this) {
- TILE -> Tile.numberOfDaysToBeConsideredForTrackingDetection
- CHIPOLO -> Chipolo.numberOfDaysToBeConsideredForTrackingDetection
- UNKNOWN -> Unknown.numberOfDaysToBeConsideredForTrackingDetection
- AIRPODS -> AirPods.numberOfDaysToBeConsideredForTrackingDetection
- AIRTAG -> AirTag.numberOfDaysToBeConsideredForTrackingDetection
- APPLE -> AppleDevice.numberOfDaysToBeConsideredForTrackingDetection
- FIND_MY -> FindMy.numberOfDaysToBeConsideredForTrackingDetection
- SAMSUNG -> SamsungDevice.numberOfDaysToBeConsideredForTrackingDetection
- GALAXY_SMART_TAG -> SmartTag.numberOfDaysToBeConsideredForTrackingDetection
- GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfDaysToBeConsideredForTrackingDetection
+ TILE -> Tile.numberOfHoursToBeConsideredForTrackingDetection
+ CHIPOLO -> Chipolo.numberOfHoursToBeConsideredForTrackingDetection
+ UNKNOWN -> Unknown.numberOfHoursToBeConsideredForTrackingDetection
+ AIRPODS -> AirPods.numberOfHoursToBeConsideredForTrackingDetection
+ AIRTAG -> AirTag.numberOfHoursToBeConsideredForTrackingDetection
+ APPLE -> AppleDevice.numberOfHoursToBeConsideredForTrackingDetection
+ FIND_MY -> FindMy.numberOfHoursToBeConsideredForTrackingDetection
+ SAMSUNG -> SamsungDevice.numberOfHoursToBeConsideredForTrackingDetection
+ GALAXY_SMART_TAG -> SmartTag.numberOfHoursToBeConsideredForTrackingDetection
+ GALAXY_SMART_TAG_PLUS -> SmartTagPlus.numberOfHoursToBeConsideredForTrackingDetection
}
}
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 76a03046..c7f22472 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
@@ -40,8 +40,8 @@ class Chipolo(val id: Int) : Device() {
override val websiteManufacturer: String
get() = "https://chipolo.net/"
- override val numberOfDaysToBeConsideredForTrackingDetection: Long
- get() = 1
+ override val numberOfHoursToBeConsideredForTrackingDetection: Long
+ get() = super.numberOfHoursToBeConsideredForTrackingDetection
override val numberOfLocationsToBeConsideredForTrackingDetectionLow: Int
get() = super.numberOfLocationsToBeConsideredForTrackingDetectionLow
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 9f2b181b..7f3c151e 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
@@ -41,8 +41,8 @@ class Tile(val id: Int) : Device(){
override val statusByteDeviceType: UInt
get() = 0u
- override val numberOfDaysToBeConsideredForTrackingDetection: Long
- get() = 1
+ override val numberOfHoursToBeConsideredForTrackingDetection: Long
+ get() = super.numberOfHoursToBeConsideredForTrackingDetection
override val numberOfLocationsToBeConsideredForTrackingDetectionLow: Int
get() = super.numberOfLocationsToBeConsideredForTrackingDetectionLow
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskCardViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskCardViewModel.kt
index 02eda66e..2e521444 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskCardViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskCardViewModel.kt
@@ -12,7 +12,6 @@ import de.seemoo.at_tracking_detection.util.risk.RiskLevel
import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
import java.text.DateFormat
import java.time.LocalDateTime
-import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import javax.inject.Inject
@@ -87,7 +86,7 @@ class RiskCardViewModel @Inject constructor(
riskColor = ContextCompat.getColor(context, R.color.risk_low)
trackersFoundModel.postValue(RiskRowViewModel(
- context.getString(R.string.no_trackers_found, RiskLevelEvaluator.RELEVANT_DAYS),
+ context.getString(R.string.no_trackers_found, RiskLevelEvaluator.RELEVANT_HOURS),
ContextCompat.getDrawable(context, R.drawable.ic_baseline_location_on_24)!!
))
lastDiscoveryModel.postValue(RiskRowViewModel(
@@ -106,7 +105,7 @@ class RiskCardViewModel @Inject constructor(
context.getString(
R.string.found_x_trackers,
totalAlerts,
- RiskLevelEvaluator.RELEVANT_DAYS
+ RiskLevelEvaluator.RELEVANT_HOURS
),
ContextCompat.getDrawable(context, R.drawable.ic_baseline_location_on_24)!!
))
@@ -128,7 +127,7 @@ class RiskCardViewModel @Inject constructor(
context.getString(
R.string.found_x_trackers,
totalAlerts,
- RiskLevelEvaluator.RELEVANT_DAYS
+ RiskLevelEvaluator.RELEVANT_HOURS
),
ContextCompat.getDrawable(context, R.drawable.ic_baseline_location_on_24)!!
))
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 000891bc..a320eb82 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -9,7 +9,6 @@ import android.transition.TransitionInflater
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.widget.Button
import android.widget.ImageButton
import androidx.activity.addCallback
import androidx.cardview.widget.CardView
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
index 427fdabd..bb93e5ac 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
@@ -2,9 +2,7 @@ package de.seemoo.at_tracking_detection.ui.tracking
import android.content.Intent
import android.net.Uri
-import android.widget.Toast
import androidx.lifecycle.*
-import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.Beacon
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.database.models.device.Connectable
@@ -13,7 +11,6 @@ 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.NotificationRepository
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import timber.log.Timber
import java.time.LocalDateTime
import javax.inject.Inject
@@ -30,7 +27,7 @@ class TrackingViewModel @Inject constructor(
val noLocationsYet = MutableLiveData(true)
- val manufacturerWebsiteUrl = MutableLiveData("https://www.apple.com/airtag/")
+ private val manufacturerWebsiteUrl = MutableLiveData("https://www.apple.com/airtag/")
val error = MutableLiveData(false)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
index 2cc4ea21..081ea4d0 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
@@ -79,7 +79,7 @@ class RiskLevelEvaluator(
// How many trackers have been relevant here as tracker
fun getNumberRelevantTrackers(): Int {
- val relevantDate = LocalDateTime.now().minusDays(RELEVANT_DAYS)
+ val relevantDate = LocalDateTime.now().minusHours(RELEVANT_HOURS)
val baseDevices: List = deviceRepository.trackingDevicesNotIgnoredSince(relevantDate)
return baseDevices.count()
@@ -87,14 +87,15 @@ class RiskLevelEvaluator(
companion object {
/** The number of days that we use to calculate the risk **/
- const val RELEVANT_DAYS: Long = 14 // Only consider beacons in the last x days, default value, can be overwritten in the specific device properties
+ const val RELEVANT_HOURS: Long = 24 // Only consider beacons in the last x hours, default value, can be overwritten in the specific device properties
private const val NUMBER_OF_NOTIFICATIONS_FOR_HIGH_RISK: Long = 2 // After x MEDIUM risk notifications (for a single device) change risk level to HIGH
private const val RELEVANT_DAYS_NOTIFICATIONS: Long = 5 // After MEDIUM risk notifications in the last x days (for a single device) change risk level to HIGH
private const val NUMBER_OF_BEACONS_BEFORE_ALARM: Int = 3 // Number of total beacons before notification is created
private const val MAX_ACCURACY_FOR_LOCATIONS: Float = 100.0F // Minimum Location accuracy for high risk
const val HOURS_AT_LEAST_UNTIL_NEXT_NOTIFICATION: Long = 8 // Minimum time difference until next notification
const val MAX_NUMBER_MEDIUM_RISK: Long = 3 // Maximum number of devices with MEDIUM risk until the total risk level is set to high
- val relevantTrackingDateDefault: LocalDateTime = LocalDateTime.now().minusDays(RELEVANT_DAYS) // Fallback Option, if possible use getRelevantTrackingDate() Function
+ val relevantTrackingDateDefault: LocalDateTime = LocalDateTime.now().minusHours(
+ RELEVANT_HOURS) // Fallback Option, if possible use getRelevantTrackingDate() Function
private val relevantNotificationDate: LocalDateTime = LocalDateTime.now().minusDays(RELEVANT_DAYS_NOTIFICATIONS)
// Default Values: A single tracker gets tracked at least for x minutes until notification is created
@@ -150,7 +151,7 @@ class RiskLevelEvaluator(
// Not ignored
// Tracker has been seen long enough
if (!device.ignore && device.firstDiscovery <= getAtLeastTrackedSince()) {
- val relevantDays: Long = device.deviceType?.getNumberOfDaysToBeConsideredForTrackingDetection() ?: RELEVANT_DAYS
+ val relevantDays: Long = device.deviceType?.getNumberOfHoursToBeConsideredForTrackingDetection() ?: RELEVANT_HOURS
val relevantTrackingDate = getRelevantTrackingDate(relevantDays)
val numberOfBeacons = beaconRepository.getNumberOfBeaconsAddress(device.address, relevantTrackingDate)
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index a7ca0607..fea7b304 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -149,8 +149,8 @@
Kein Risiko
Mittleres Risiko
Hohes Risiko
- Keine Tracker gefunden in den letzten %d Tagen
- %d Tracker gefunden in den letzten %d Tagen
+ Keine Tracker gefunden in den letzten %d Stunden
+ %d Tracker gefunden in den letzten %d Stunden
Letzter Scan: %s
Letzter Tracker gefunden am %s
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 56ce7fee..5af4fc31 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -173,8 +173,8 @@
No risk
Medium risk
High risk
- No trackers found during the last %d days
- Detected %d tracker during the last %d days
+ No trackers found during the last %d hours
+ Detected %d tracker during the last %d hours
Last scan: %s
Last tracker discovered at %s
How does AirGuard work?
From c500fbf160319de9241a648db9954375160b2f83 Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Thu, 21 Sep 2023 15:16:30 +0200
Subject: [PATCH 054/154] Fixed an issue where the versionProvider was removed
in the LocationProvidern
---
.../at_tracking_detection/detection/LocationProvider.kt | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
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 fe5eb3a9..ceef02c4 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
@@ -8,6 +8,7 @@ import android.location.LocationManager
import android.os.*
import androidx.core.content.ContextCompat
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
+import de.seemoo.at_tracking_detection.util.BuildVersionProvider
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@@ -16,7 +17,8 @@ import javax.inject.Singleton
@Singleton
open class LocationProvider @Inject constructor(
- private val locationManager: LocationManager): LocationListener {
+ private val locationManager: LocationManager,
+ private val versionProvider: BuildVersionProvider): LocationListener {
private val handler: Handler = Handler(Looper.getMainLooper())
private var bestLastLocation: Location? = null
From 2a089e20c725f3c67ec7dc0d7df7c403cb9a9c7f Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Fri, 20 Oct 2023 19:48:17 +0200
Subject: [PATCH 055/154] Update 2.1 for F-Droid
---
api.properties | 2 +-
.../ATTrackingDetectionApplication.kt | 38 ++++++++----------
fastlane/Fastfile | 6 ---
.../metadata/android/de-DE/changelogs/35.txt | 4 --
.../metadata/android/de-DE/changelogs/38.txt | 7 ++++
.../android/de-DE/full_description.txt | 4 +-
.../metadata/android/en-US/changelogs/35.txt | 4 --
.../metadata/android/en-US/changelogs/38.txt | 7 ++++
.../{1_en-US.png => 1_en-US.} | Bin
9 files changed, 34 insertions(+), 38 deletions(-)
delete mode 100644 fastlane/metadata/android/de-DE/changelogs/35.txt
create mode 100644 fastlane/metadata/android/de-DE/changelogs/38.txt
delete mode 100644 fastlane/metadata/android/en-US/changelogs/35.txt
create mode 100644 fastlane/metadata/android/en-US/changelogs/38.txt
rename fastlane/metadata/android/en-US/images/tenInchScreenshots/{1_en-US.png => 1_en-US.} (100%)
diff --git a/api.properties b/api.properties
index 78207fc3..a35e0002 100644
--- a/api.properties
+++ b/api.properties
@@ -1,2 +1,2 @@
-API_KEY="D1nvdpzB.zvCS4e1SmLRTULoql5wUftzgRrTJmj5R"
+API_KEY="wfLvEz5s.bvrJ1CGKuvqeZB1q8v7oTirMMuWQjqjZ"
API_BASE_ADDRESS="https://tpe.seemoo.tu-darmstadt.de/api/"
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt
index 0d1ecba6..630c765b 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt
@@ -7,7 +7,6 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
-import android.location.Location
import android.os.Build
import android.util.Log
import androidx.core.content.ContextCompat
@@ -20,9 +19,7 @@ import de.seemoo.at_tracking_detection.database.repository.DeviceRepository
import de.seemoo.at_tracking_detection.database.repository.LocationRepository
import de.seemoo.at_tracking_detection.database.repository.NotificationRepository
import de.seemoo.at_tracking_detection.detection.LocationProvider
-import de.seemoo.at_tracking_detection.detection.LocationRequester
import de.seemoo.at_tracking_detection.notifications.NotificationService
-import de.seemoo.at_tracking_detection.statistics.api.Api
import de.seemoo.at_tracking_detection.ui.OnboardingActivity
import de.seemoo.at_tracking_detection.util.ATTDLifecycleCallbacks
import de.seemoo.at_tracking_detection.util.SharedPrefs
@@ -32,7 +29,6 @@ import fr.bipi.tressence.file.FileLoggerTree
import timber.log.Timber
import java.io.File
import java.time.LocalDateTime
-import java.util.*
import javax.inject.Inject
@@ -121,23 +117,23 @@ class ATTrackingDetectionApplication : Application(), Configuration.Provider {
BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
if (BuildConfig.DEBUG) {
- // Get a location for testing
- Timber.d("Request location")
- val startTime = Date()
- val locationRequester: LocationRequester = object : LocationRequester() {
- override fun receivedAccurateLocationUpdate(location: Location) {
- val endTime = Date()
- val duration = (endTime.time - startTime.time) / 1000
- Timber.d("Got location $location after $duration s")
- }
- }
- val location = locationProvider.lastKnownOrRequestLocationUpdates(locationRequester, 20_000L)
- if (location != null) {
- Timber.d("Using last known location")
- }
-
- // Printing time zone and user agent
- Timber.d("Timezone: ${Api.TIME_ZONE} useragent ${Api.USER_AGENT}")
+// // Get a location for testing
+// Timber.d("Request location")
+// val startTime = Date()
+// val locationRequester: LocationRequester = object : LocationRequester() {
+// override fun receivedAccurateLocationUpdate(location: Location) {
+// val endTime = Date()
+// val duration = (endTime.time - startTime.time) / 1000
+// Timber.d("Got location $location after $duration s")
+// }
+// }
+// val location = locationProvider.lastKnownOrRequestLocationUpdates(locationRequester, 20_000L)
+// if (location != null) {
+// Timber.d("Using last known location")
+// }
+//
+// // Printing time zone and user agent
+// Timber.d("Timezone: ${Api.TIME_ZONE} useragent ${Api.USER_AGENT}")
}
}
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 2515af37..19c557cc 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -35,10 +35,4 @@ platform :android do
gradle(task: "clean assembleRelease")
upload_to_play_store
end
-
- desc "Fetch Screenshots and metadata from Google Play"
- lane :update_metadta do
- download_from_play_store
- end
-
end
diff --git a/fastlane/metadata/android/de-DE/changelogs/35.txt b/fastlane/metadata/android/de-DE/changelogs/35.txt
deleted file mode 100644
index c0827b9f..00000000
--- a/fastlane/metadata/android/de-DE/changelogs/35.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-• Wir führen eine Umfrage über Stalking durch und wollen herausfinden, wie Nutzer:innen besser vor diesen Attacken geschützt werden können.
-• Die Tracking Erkennung wurde verbessert.
-• Wir haben die Navigation in der App verbessert. Du findest nun alle gefundenen Geräte im entsprechenden Tab,
-• Gerätefilter wurden verbessert. Sie sind nun einfacher zu erkennen und zu verstehen.
\ No newline at end of file
diff --git a/fastlane/metadata/android/de-DE/changelogs/38.txt b/fastlane/metadata/android/de-DE/changelogs/38.txt
new file mode 100644
index 00000000..cb24d751
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/38.txt
@@ -0,0 +1,7 @@
+- Suche nach Trackern mittels eines detaillierten Scans
+- Sortieroptionen für den manuellen Scan
+- Manueller Scan kann pausiert werden
+- Geräte aus den Scan-Benachrichtigungen entfernen
+- App-Sprache kann geändert werden
+- Benachrichtigungen müssen in Android 13 akzeptiert werden
+- Thematische Icons
\ No newline at end of file
diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt
index af003d9a..05755834 100644
--- a/fastlane/metadata/android/de-DE/full_description.txt
+++ b/fastlane/metadata/android/de-DE/full_description.txt
@@ -5,7 +5,7 @@ Diese Tracker sind nur so groß wie eine Münze und ideal, um Android Nutzer zu
Nachdem ein AirTag gefunden wurde, kannst du diesen klingeln lassen oder mit einem manuellen Scan auf die Suche gehen. Findest du ein Gerät, dann raten wir dir es zu deaktivieren, damit es nicht mehr deinen Standort verfolgt.
-Die App speichert deinen Standort nur lokal, um dir später zu zeigen an welchen Orten ein Tracker dir gefolgt ist. Persönliche Daten teilt diese App niemals.
+Die App speichert deinen Standort nur lokal, um dir später zu zeigen an welchen Orten ein Tracker die gefolgt ist. Persönliche Daten teilt diese App niemals.
Wenn niemand dich versucht zu starken über einen AirTag, dann wird die App dich auch nicht stören.
@@ -28,4 +28,4 @@ https://tpe.seemoo.tu-darmstadt.de/privacy-policy.html
Rechtliches
AirTag, FindMy und iOS are eingetragene Marken von Apple Inc.
-Dieses Projekt ist keine Zusammenarbeit mit Apple Inc.
+Dieses Projekt ist keine Zusammenarbeit mit Apple Inc.
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/35.txt b/fastlane/metadata/android/en-US/changelogs/35.txt
deleted file mode 100644
index d07a9699..00000000
--- a/fastlane/metadata/android/en-US/changelogs/35.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-• We are conducting a survey about stalking and how AirGuard can protect users better against these attacks.
-• Tracking detection has been enhanced.
-• We enhanced the navigation of the app. You can now find all discovered devices in the devices tab.
-• Device filters have been enhanced. They are now easier to discover and to understand.
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/38.txt b/fastlane/metadata/android/en-US/changelogs/38.txt
new file mode 100644
index 00000000..27d96343
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/38.txt
@@ -0,0 +1,7 @@
+- Search for scanners using a detailed scan
+- Manual scan sort options
+- Manual scan can be paused
+- Remove devices from scan notifications
+- App language can be changed
+- Notifications have to be accepted in Android 13
+- Themed icons
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US.
similarity index 100%
rename from fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US.png
rename to fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US.
From 3b8f8d6368f42f4652ed95b5d06509e1d17d9454 Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Tue, 27 Jun 2023 10:01:45 +0200
Subject: [PATCH 056/154] 2.0 F-Droid Release version
---
fastlane/Appfile | 2 +-
fastlane/metadata/android/de-DE/changelogs/37.txt | 5 +++++
fastlane/metadata/android/en-US/changelogs/37.txt | 6 ++++++
3 files changed, 12 insertions(+), 1 deletion(-)
create mode 100644 fastlane/metadata/android/de-DE/changelogs/37.txt
create mode 100644 fastlane/metadata/android/en-US/changelogs/37.txt
diff --git a/fastlane/Appfile b/fastlane/Appfile
index 9314bca2..d7e1f266 100644
--- a/fastlane/Appfile
+++ b/fastlane/Appfile
@@ -1,2 +1,2 @@
-json_key_file("../AirGuard-Resources/Keys/pc-api-8608386712729798797-388-62cf652e4a65.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
+json_key_file("/Users/seemoo/Work/Research/AirGuard/AirGuard-Resources/Keys/pc-api-8608386712729798797-388-62cf652e4a65.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name("de.seemoo.at_tracking_detection.release") # e.g. com.krausefx.app
diff --git a/fastlane/metadata/android/de-DE/changelogs/37.txt b/fastlane/metadata/android/de-DE/changelogs/37.txt
new file mode 100644
index 00000000..65a6608c
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/37.txt
@@ -0,0 +1,5 @@
+- Samsung SmartTags werden unterstützt
+- Chipolo Tags werden unterstützt
+- Verbesserte Tracking Erkennung
+- Verbessertes Nutzerinterface für manuelles Scannen
+- Erkennung von AirTags die mit einem iPhone verbunden sind
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/37.txt b/fastlane/metadata/android/en-US/changelogs/37.txt
new file mode 100644
index 00000000..2425a7cc
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/37.txt
@@ -0,0 +1,6 @@
+- Support for Samsung SmartTags
+- Support for Chipolo Tags
+- Improved tracking detecting
+- Improved risk evaluation
+- Improved user interface for manual scanning
+- The app can now detect AirTags which are connected to an iPhone
\ No newline at end of file
From 8e5f03518c3256fe351a294378f8db63389a2479 Mon Sep 17 00:00:00 2001
From: Peter Dave Hello
Date: Thu, 28 Sep 2023 20:58:50 +0800
Subject: [PATCH 057/154] Fix Google Play badge in README.md
The old endpoint is longer maintained and project archived:
- https://github.com/cvzi/playshields
The alternative made by the same author can simply replace the old one:
- https://github.com/cvzi/play
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 9f2ba421..11db355b 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-
+
From 69199e5fda4dc61bbcdbed5cb25868af03ecf111 Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Sun, 12 Nov 2023 11:50:02 +0100
Subject: [PATCH 058/154] Working on fixing SendStatisticsWorker
---
.../at_tracking_detection/hilt/ApiModule.kt | 10 +++-
.../statistics/SendStatisticsWorker.kt | 50 ++++++++++++++-----
.../ui/debug/DebugFragment.kt | 2 +-
.../util/ble/BLEScanner.kt | 2 +-
.../worker/BackgroundWorkBuilder.kt | 7 +++
.../worker/BackgroundWorkScheduler.kt | 6 +++
.../worker/WorkerConstants.kt | 1 +
7 files changed, 61 insertions(+), 17 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/hilt/ApiModule.kt b/app/src/main/java/de/seemoo/at_tracking_detection/hilt/ApiModule.kt
index a882c547..ae5f3488 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/hilt/ApiModule.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/hilt/ApiModule.kt
@@ -13,16 +13,22 @@ import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.time.LocalDateTime
+import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object ApiModule {
-
+ val HTTP_TIMEOUT: Long = 60
@Provides
@Singleton
- fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().build()
+ fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder()
+ .callTimeout(HTTP_TIMEOUT, TimeUnit.SECONDS)
+ .connectTimeout(HTTP_TIMEOUT, TimeUnit.SECONDS)
+ .readTimeout(HTTP_TIMEOUT, TimeUnit.SECONDS)
+ .writeTimeout(HTTP_TIMEOUT, TimeUnit.SECONDS)
+ .build()
@Provides
@Singleton
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/statistics/SendStatisticsWorker.kt b/app/src/main/java/de/seemoo/at_tracking_detection/statistics/SendStatisticsWorker.kt
index bd6dc875..c04a01b4 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/statistics/SendStatisticsWorker.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/statistics/SendStatisticsWorker.kt
@@ -22,24 +22,38 @@ class SendStatisticsWorker @AssistedInject constructor(
) : CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
- if (BuildConfig.DEBUG) {
- Timber.d("Not sending any data. Debug mode")
- return Result.success()
- }
+// if (BuildConfig.DEBUG) {
+// Timber.d("Not sending any data. Debug mode")
+// return Result.success()
+// }
var token = SharedPrefs.token
val lastDataDonation = SharedPrefs.lastDataDonation ?: LocalDateTime.MIN
- if (!api.ping().isSuccessful) {
- Timber.e("Server not available!")
+ try {
+ if (!api.ping().isSuccessful) {
+ Timber.e("Server not available!")
+ return Result.retry()
+ }
+ }catch (e: Error) {
+ Timber.e("Failed pinging server")
+ Timber.e(e.message)
return Result.retry()
}
+
if (token == null) {
- val response = api.getToken().body() ?: return Result.retry()
- token = response.token
- SharedPrefs.token = token
+ try {
+ val response = api.getToken().body() ?: return Result.retry()
+ token = response.token
+ SharedPrefs.token = token
+ }catch (e: Error) {
+ Timber.e("Failed getting token")
+ Timber.e(e.message)
+ return Result.retry()
+ }
}
+
val oneWeekAgo = LocalDateTime.now().minusWeeks(1)
val uploadDateTime: LocalDateTime = if (lastDataDonation > oneWeekAgo) {
lastDataDonation
@@ -48,12 +62,10 @@ class SendStatisticsWorker @AssistedInject constructor(
}
val devices = deviceRepository.getDeviceBeaconsSinceDate(uploadDateTime)
- // This makes sure that no devices will be sent twice. If the donation fails, then the app
- // will upload newer data the next time.
- SharedPrefs.lastDataDonation = LocalDateTime.now()
if (devices.isEmpty()) {
Timber.d("Nothing to send...")
+ SharedPrefs.lastDataDonation = LocalDateTime.now()
return Result.success()
}
@@ -68,7 +80,19 @@ class SendStatisticsWorker @AssistedInject constructor(
}
}
- if (!api.donateData(token = token, devices=devices).isSuccessful) {
+ if (!BuildConfig.DEBUG) {
+ // This makes sure that no devices will be sent twice. If the donation fails, then the app
+ // will upload newer data the next time.
+ SharedPrefs.lastDataDonation = LocalDateTime.now()
+ }
+ Timber.d("Trying to upload ${devices.size}")
+ try {
+ if (!api.donateData(token = token, devices=devices).isSuccessful) {
+ return Result.retry()
+ }
+ }catch (e: Error) {
+ Timber.e("Failed uploading devices")
+ Timber.e(e.message)
return Result.retry()
}
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 0fa294d6..1341f56e 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
@@ -100,7 +100,7 @@ class DebugFragment : Fragment() {
}
}
view.findViewById(R.id.donate_data)?.setOnClickListener {
- backgroundWorkScheduler.scheduleShareData()
+ backgroundWorkScheduler.scheduleShareDataDebug()
}
view.findViewById(R.id.button4)?.setOnClickListener {
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 a15469fb..f0bc565e 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
@@ -99,7 +99,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)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkBuilder.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkBuilder.kt
index 7868557d..7a6fa0dc 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkBuilder.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkBuilder.kt
@@ -28,6 +28,13 @@ class BackgroundWorkBuilder @Inject constructor() {
.setConstraints(buildConstraints())
.build()
+ /*Send statistics now*/
+ fun buildSendStatisticsWorkerDebug(): OneTimeWorkRequest =
+ OneTimeWorkRequestBuilder().addTag(WorkerConstants.ONETIME_SEND_STATISTICS_WORKER)
+ .setBackoffCriteria(BackoffPolicy.LINEAR, WorkerConstants.KIND_DELAY, TimeUnit.HOURS)
+ .setConstraints(buildConstraints())
+ .build()
+
fun buildTrackingDetectorWorker(): OneTimeWorkRequest =
OneTimeWorkRequestBuilder().addTag(WorkerConstants.TRACKING_DETECTION_WORKER)
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkerConstants.KIND_DELAY, TimeUnit.MINUTES)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
index 284deb4e..fe9a35b2 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
@@ -50,6 +50,12 @@ class BackgroundWorkScheduler @Inject constructor(
backgroundWorkBuilder.buildSendStatisticsWorker()
).also { it.logOperationSchedule(WorkerConstants.PERIODIC_SEND_STATISTICS_WORKER) }
+ fun scheduleShareDataDebug() = workManager.enqueueUniqueWork(
+ WorkerConstants.ONETIME_SEND_STATISTICS_WORKER,
+ ExistingWorkPolicy.APPEND_OR_REPLACE,
+ backgroundWorkBuilder.buildSendStatisticsWorkerDebug()
+ ).also { it.logOperationSchedule(WorkerConstants.ONETIME_SEND_STATISTICS_WORKER) }
+
fun removeShareData() =
workManager.cancelUniqueWork(WorkerConstants.PERIODIC_SEND_STATISTICS_WORKER)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/WorkerConstants.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/WorkerConstants.kt
index dc8171ed..8578c0d5 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/WorkerConstants.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/WorkerConstants.kt
@@ -5,6 +5,7 @@ object WorkerConstants {
const val MIN_HOURS_TO_NEXT_SEND_STATISTICS = 4L
const val PERIODIC_SCAN_WORKER = "PeriodicScanWorker"
const val PERIODIC_SEND_STATISTICS_WORKER = "PeriodicSendStatisticsWorker"
+ const val ONETIME_SEND_STATISTICS_WORKER = "OneTimeSendStatisticsWorker"
const val TRACKING_DETECTION_WORKER = "TrackingDetectionWorker"
const val IGNORE_DEVICE_WORKER = "IgnoreDeviceWorker"
const val FALSE_ALARM_WORKER = "FalseAlarmWorker"
From f35fcdb234e895143567a5d3d75a9e093b816cac Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Sun, 12 Nov 2023 19:31:27 +0100
Subject: [PATCH 059/154] Changing proguard rules
This fixes a bug caused by proguard, which would let any network connections fail.
---
app/proguard-rules.pro | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 38e3512b..c1071cd2 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -56,9 +56,20 @@
# Keep generic signature of Call (R8 full mode strips signatures from non-kept items).
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
+# Keep inherited services.
+-if interface * { @retrofit2.http.* ; }
+-keep,allowobfuscation interface * extends <1>
+
# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
+# R8 full mode strips generic signatures from return types if not kept.
+-if interface * { @retrofit2.http.* public *** *(...); }
+-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
+
+# R8 full mode strips generic signatures from return types if not kept.
+-keep,allowobfuscation,allowshrinking class retrofit2.Response
+
-keep class de.seemoo.at_tracking_detection.** { *; }
\ No newline at end of file
From 1cd320f506e16de191065782de15b905342d2287 Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Tue, 14 Nov 2023 10:16:06 +0100
Subject: [PATCH 060/154] Reducing duplicate devices uploads
---
.../database/daos/DeviceDao.kt | 2 +-
.../relations/DeviceBeaconNotification.kt | 4 +--
.../statistics/SendStatisticsWorker.kt | 28 ++++++++++++++-----
3 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/DeviceDao.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/DeviceDao.kt
index a65e3d47..b6472947 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/DeviceDao.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/DeviceDao.kt
@@ -82,7 +82,7 @@ interface DeviceDao {
@Transaction
@RewriteQueriesToDropUnusedColumns
- @Query("SELECT * FROM device JOIN beacon ON beacon.deviceAddress = deviceAddress WHERE beacon.receivedAt >= :dateTime")
+ @Query("SELECT * FROM device WHERE lastSeen >= :dateTime")
suspend fun getDeviceBeaconsSince(dateTime: LocalDateTime): List
@Transaction
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/relations/DeviceBeaconNotification.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/relations/DeviceBeaconNotification.kt
index 87c8337a..6c821179 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/relations/DeviceBeaconNotification.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/relations/DeviceBeaconNotification.kt
@@ -18,11 +18,11 @@ data class DeviceBeaconNotification(
val lastSeen: LocalDateTime,
val deviceType: DeviceType,
@Relation(parentColumn = "address", entityColumn = "deviceAddress")
- val beacons: List,
+ var beacons: List,
@Relation(
parentColumn = "address",
entityColumn = "deviceAddress",
entity = Notification::class
)
- val notifications: List
+ var notifications: List
)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/statistics/SendStatisticsWorker.kt b/app/src/main/java/de/seemoo/at_tracking_detection/statistics/SendStatisticsWorker.kt
index c04a01b4..c7548fd3 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/statistics/SendStatisticsWorker.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/statistics/SendStatisticsWorker.kt
@@ -22,10 +22,11 @@ class SendStatisticsWorker @AssistedInject constructor(
) : CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
-// if (BuildConfig.DEBUG) {
-// Timber.d("Not sending any data. Debug mode")
-// return Result.success()
-// }
+ if (BuildConfig.DEBUG) {
+ Timber.d("Not sending any data. Debug mode")
+ return Result.success()
+ }
+
var token = SharedPrefs.token
val lastDataDonation = SharedPrefs.lastDataDonation ?: LocalDateTime.MIN
@@ -55,12 +56,17 @@ class SendStatisticsWorker @AssistedInject constructor(
val oneWeekAgo = LocalDateTime.now().minusWeeks(1)
- val uploadDateTime: LocalDateTime = if (lastDataDonation > oneWeekAgo) {
+ var uploadDateTime: LocalDateTime = if (lastDataDonation > oneWeekAgo) {
lastDataDonation
}else {
oneWeekAgo
}
+ if (BuildConfig.DEBUG) {
+ uploadDateTime = LocalDateTime.now().minusDays(1)
+ }
+
+ // Get all devices last seen, since the last upload time
val devices = deviceRepository.getDeviceBeaconsSinceDate(uploadDateTime)
if (devices.isEmpty()) {
@@ -72,12 +78,20 @@ class SendStatisticsWorker @AssistedInject constructor(
// Remove sensitive data
devices.forEach {
it.address = ""
+ // Remove beacons that have already been uploaded
+ it.beacons = it.beacons.filter { beacon ->
+ beacon.receivedAt >= uploadDateTime
+ }
it.beacons.forEach { beacon ->
// beacon.latitude = null
// beacon.longitude = null
beacon.locationId = null
beacon.deviceAddress = ""
}
+ // Remove notifications that have already been uploaded
+ it.notifications = it.notifications.filter { notification ->
+ notification.createdAt >= uploadDateTime
+ }
}
if (!BuildConfig.DEBUG) {
@@ -85,7 +99,7 @@ class SendStatisticsWorker @AssistedInject constructor(
// will upload newer data the next time.
SharedPrefs.lastDataDonation = LocalDateTime.now()
}
- Timber.d("Trying to upload ${devices.size}")
+ Timber.d("Trying to upload ${devices.size} devices")
try {
if (!api.donateData(token = token, devices=devices).isSuccessful) {
return Result.retry()
@@ -97,7 +111,7 @@ class SendStatisticsWorker @AssistedInject constructor(
}
Timber.d("${devices.size} devices shared!")
-
+ SharedPrefs.lastDataDonation = LocalDateTime.now()
return Result.success()
}
}
\ No newline at end of file
From a9e495ebc7463c749d4b9b047d4a6e75c6cd3e29 Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Tue, 14 Nov 2023 10:25:42 +0100
Subject: [PATCH 061/154] F-Droid 2.1.1 Release
---
api.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/api.properties b/api.properties
index a35e0002..5ce2e9b8 100644
--- a/api.properties
+++ b/api.properties
@@ -1,2 +1,2 @@
-API_KEY="wfLvEz5s.bvrJ1CGKuvqeZB1q8v7oTirMMuWQjqjZ"
+API_KEY="UCKgXwP.Dvmxyn1UygvnJfy2DV16OjbmHia4xXpd"
API_BASE_ADDRESS="https://tpe.seemoo.tu-darmstadt.de/api/"
\ No newline at end of file
From eacf8bd70490d660068b963e3c87fa4a5d6a11fc Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Tue, 14 Nov 2023 10:36:31 +0100
Subject: [PATCH 062/154] Updating F-Droid Metadata
---
fastlane/Fastfile | 6 ++++++
fastlane/metadata/android/de-DE/changelogs/37.txt | 5 -----
.../android/de-DE/changelogs/{38.txt => 39.txt} | 1 +
fastlane/metadata/android/en-US/changelogs/37.txt | 6 ------
.../android/en-US/changelogs/{38.txt => 39.txt} | 1 +
.../tenInchScreenshots/{1_en-US. => 1_en-US.png} | Bin
6 files changed, 8 insertions(+), 11 deletions(-)
delete mode 100644 fastlane/metadata/android/de-DE/changelogs/37.txt
rename fastlane/metadata/android/de-DE/changelogs/{38.txt => 39.txt} (84%)
delete mode 100644 fastlane/metadata/android/en-US/changelogs/37.txt
rename fastlane/metadata/android/en-US/changelogs/{38.txt => 39.txt} (81%)
rename fastlane/metadata/android/en-US/images/tenInchScreenshots/{1_en-US. => 1_en-US.png} (100%)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 19c557cc..b95c2cf3 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -35,4 +35,10 @@ platform :android do
gradle(task: "clean assembleRelease")
upload_to_play_store
end
+
+ desc "Fetch Screenshots and metadata from Google Play"
+ lane :update_metadata do
+ download_from_play_store
+ end
+
end
diff --git a/fastlane/metadata/android/de-DE/changelogs/37.txt b/fastlane/metadata/android/de-DE/changelogs/37.txt
deleted file mode 100644
index 65a6608c..00000000
--- a/fastlane/metadata/android/de-DE/changelogs/37.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-- Samsung SmartTags werden unterstützt
-- Chipolo Tags werden unterstützt
-- Verbesserte Tracking Erkennung
-- Verbessertes Nutzerinterface für manuelles Scannen
-- Erkennung von AirTags die mit einem iPhone verbunden sind
\ No newline at end of file
diff --git a/fastlane/metadata/android/de-DE/changelogs/38.txt b/fastlane/metadata/android/de-DE/changelogs/39.txt
similarity index 84%
rename from fastlane/metadata/android/de-DE/changelogs/38.txt
rename to fastlane/metadata/android/de-DE/changelogs/39.txt
index cb24d751..2fda3d63 100644
--- a/fastlane/metadata/android/de-DE/changelogs/38.txt
+++ b/fastlane/metadata/android/de-DE/changelogs/39.txt
@@ -1,3 +1,4 @@
+Diese Version behebt kleinere Probleme in Version 2.1
- Suche nach Trackern mittels eines detaillierten Scans
- Sortieroptionen für den manuellen Scan
- Manueller Scan kann pausiert werden
diff --git a/fastlane/metadata/android/en-US/changelogs/37.txt b/fastlane/metadata/android/en-US/changelogs/37.txt
deleted file mode 100644
index 2425a7cc..00000000
--- a/fastlane/metadata/android/en-US/changelogs/37.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-- Support for Samsung SmartTags
-- Support for Chipolo Tags
-- Improved tracking detecting
-- Improved risk evaluation
-- Improved user interface for manual scanning
-- The app can now detect AirTags which are connected to an iPhone
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/38.txt b/fastlane/metadata/android/en-US/changelogs/39.txt
similarity index 81%
rename from fastlane/metadata/android/en-US/changelogs/38.txt
rename to fastlane/metadata/android/en-US/changelogs/39.txt
index 27d96343..f952190e 100644
--- a/fastlane/metadata/android/en-US/changelogs/38.txt
+++ b/fastlane/metadata/android/en-US/changelogs/39.txt
@@ -1,3 +1,4 @@
+This version fixes some minor issues in version 2.1
- Search for scanners using a detailed scan
- Manual scan sort options
- Manual scan can be paused
diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US. b/fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US.png
similarity index 100%
rename from fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US.
rename to fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US.png
From 1dacfc1630a623a5de28ec062ea77bc3e9962536 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 24 Sep 2023 12:59:51 +0200
Subject: [PATCH 063/154] Rebasing Main onto current branch
---
.../at_tracking_detection/detection/LocationProvider.kt | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
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 ceef02c4..fe5eb3a9 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
@@ -8,7 +8,6 @@ import android.location.LocationManager
import android.os.*
import androidx.core.content.ContextCompat
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
-import de.seemoo.at_tracking_detection.util.BuildVersionProvider
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@@ -17,8 +16,7 @@ import javax.inject.Singleton
@Singleton
open class LocationProvider @Inject constructor(
- private val locationManager: LocationManager,
- private val versionProvider: BuildVersionProvider): LocationListener {
+ private val locationManager: LocationManager): LocationListener {
private val handler: Handler = Handler(Looper.getMainLooper())
private var bestLastLocation: Location? = null
From e086ed0a80d46d64c88d87c3f6ad3f1ac043a1ba Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 2 Oct 2023 12:28:01 +0200
Subject: [PATCH 064/154] ObserveTrackerWorker now calls ScanBluetoothWorker
manually before checking Observation criteria. Clarify explanation how
Observe Tracker works.
---
.../ui/tracking/ObserveTrackerFragment.kt | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerFragment.kt
index 89576d83..4bf67f1c 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerFragment.kt
@@ -37,6 +37,11 @@ class ObserveTrackerFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+// if (deviceAddress != null) {
+// val text = view.findViewById(R.id.changing_id_text)
+// text.visibility = View.VISIBLE
+// }
+
val observationButton = view.findViewById(R.id.start_observation_button)
if (deviceAddress != null) {
From 49b31928e6a6104b33aea7ad7b211275ab0d3e75 Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Thu, 18 Jan 2024 10:54:47 +0100
Subject: [PATCH 065/154] Updating version in Manifest Updating Tests to run
with new LocationProvider changes
---
app/build.gradle | 4 ++--
.../LocationProviderTest.kt | 4 ++--
.../ScanBluetoothWorkerTest.kt | 16 ++++++++--------
3 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index b67db716..7d94a5a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -26,8 +26,8 @@ android {
applicationId "de.seemoo.at_tracking_detection"
minSdkVersion 28
targetSdk = 34
- versionCode 38
- versionName "2.1"
+ versionCode 40
+ versionName "2.1.1"
buildConfigField "String", "API_KEY", apiProperties["API_KEY"]
buildConfigField "String", "API_BASE_ADDRESS", apiProperties["API_BASE_ADDRESS"]
diff --git a/app/src/androidTest/java/de/seemoo/at_tracking_detection/LocationProviderTest.kt b/app/src/androidTest/java/de/seemoo/at_tracking_detection/LocationProviderTest.kt
index 6c62f5d0..a1a46cf4 100644
--- a/app/src/androidTest/java/de/seemoo/at_tracking_detection/LocationProviderTest.kt
+++ b/app/src/androidTest/java/de/seemoo/at_tracking_detection/LocationProviderTest.kt
@@ -26,7 +26,7 @@ class LocationProviderTest {
val context = ATTrackingDetectionApplication.getAppContext()
val locationManager = context.getSystemService()
assert(locationManager != null)
- val locationProvider = LocationProvider(locationManager!!, DefaultBuildVersionProvider())
+ val locationProvider = LocationProvider(locationManager!!)
// Getting the current location
val startTime = LocalDateTime.now()
@@ -49,7 +49,7 @@ class LocationProviderTest {
val context = ATTrackingDetectionApplication.getAppContext()
val locationManager = context.getSystemService()
assert(locationManager != null)
- val locationProvider = LocationProvider(locationManager!!, TestBuildVersionProvider(21))
+ val locationProvider = LocationProvider(locationManager!!)
// Getting the current location
val startTime = LocalDateTime.now()
diff --git a/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanBluetoothWorkerTest.kt b/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanBluetoothWorkerTest.kt
index 463d4371..fddaf3f9 100644
--- a/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanBluetoothWorkerTest.kt
+++ b/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanBluetoothWorkerTest.kt
@@ -75,7 +75,7 @@ class ScanBluetoothWorkerTest {
val deviceRepository = DatabaseModule.provideDeviceRepository(DatabaseModule.provideDeviceDao(db))
val scanRepository = DatabaseModule.provideScanRepository(DatabaseModule.provideScanDao(db))
val locationRepository = DatabaseModule.provideLocationRepository(DatabaseModule.provideLocationDao(db))
- val locationProvider = LocationProvider(context.getSystemService()!!, DefaultBuildVersionProvider())
+ val locationProvider = LocationProvider(context.getSystemService()!!)
val notificationService = ATTrackingDetectionApplication.getCurrentApp()!!.notificationService
val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()!!.backgroundWorkScheduler
@@ -236,13 +236,13 @@ class ScanBluetoothWorkerTest {
}
}
-class TestLocationProvider(private val lastLocationIsNull: Boolean, private val locationDelayMillis: Long, locationManager: LocationManager, versionProvider: BuildVersionProvider) : LocationProvider(locationManager, versionProvider) {
- override fun getLastLocation(checkRequirements: Boolean): Location? {
- if (lastLocationIsNull) {
- return null
- }
- return super.getLastLocation(checkRequirements)
- }
+class TestLocationProvider(private val lastLocationIsNull: Boolean, private val locationDelayMillis: Long, locationManager: LocationManager, versionProvider: BuildVersionProvider) : LocationProvider(locationManager) {
+// override fun getLastLocation(checkRequirements: Boolean): Location? {
+// if (lastLocationIsNull) {
+// return null
+// }
+// return super.getLastLocation(checkRequirements)
+// }
override fun lastKnownOrRequestLocationUpdates(
locationRequester: LocationRequester,
From 76c9390b3bf45c4367fe717d6cc7e6d73d5bb996 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 21 Jan 2024 17:47:54 +0100
Subject: [PATCH 066/154] some optimizations to the processing of manual scan
items
---
.../ui/scan/BluetoothDeviceAdapter.kt | 15 ++++++++-------
.../at_tracking_detection/ui/scan/ScanFragment.kt | 9 ++++-----
.../ui/scan/ScanViewModel.kt | 13 ++++++++-----
.../at_tracking_detection/util/ble/BLEScanner.kt | 9 +++++++--
4 files changed, 27 insertions(+), 19 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
index 570cf7c7..51ca7c02 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
@@ -13,7 +13,7 @@ import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Compani
import de.seemoo.at_tracking_detection.databinding.ItemScanResultBinding
class BluetoothDeviceAdapter:
- ListAdapter(Companion) {
+ ListAdapter(BluetoothDeviceDiffCallback()) {
class ScanResultViewHolder(private val binding: ItemScanResultBinding) :
RecyclerView.ViewHolder(binding.root) {
@@ -43,13 +43,14 @@ class BluetoothDeviceAdapter:
.navigate(directions)
}
}
+}
- companion object : DiffUtil.ItemCallback() {
- override fun areContentsTheSame(oldItem: ScanResult, newItem: ScanResult): Boolean =
- getPublicKey(oldItem) == getPublicKey(newItem)
-
- override fun areItemsTheSame(oldItem: ScanResult, newItem: ScanResult): Boolean =
- oldItem == newItem
+class BluetoothDeviceDiffCallback: DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: ScanResult, newItem: ScanResult): Boolean {
+ return oldItem.device.address == newItem.device.address
}
+ override fun areContentsTheSame(oldItem: ScanResult, newItem: ScanResult): Boolean {
+ return (oldItem.device.address == newItem.device.address) && (oldItem.rssi == newItem.rssi)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
index dd93e75c..f305256b 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
@@ -26,6 +26,9 @@ class ScanFragment : Fragment() {
private val scanViewModel: ScanViewModel by viewModels()
+ private val bluetoothDeviceAdapterHighRisk = BluetoothDeviceAdapter()
+ private val bluetoothDeviceAdapterLowRisk = BluetoothDeviceAdapter()
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -33,24 +36,19 @@ class ScanFragment : Fragment() {
): View {
val binding: FragmentScanBinding =
DataBindingUtil.inflate(inflater, R.layout.fragment_scan, container, false)
- val bluetoothDeviceAdapterHighRisk = BluetoothDeviceAdapter()
- val bluetoothDeviceAdapterLowRisk = BluetoothDeviceAdapter()
binding.adapterHighRisk = bluetoothDeviceAdapterHighRisk
binding.adapterLowRisk = bluetoothDeviceAdapterLowRisk
binding.lifecycleOwner = viewLifecycleOwner
binding.vm = scanViewModel
- // TODO: possible to optimize this???
scanViewModel.bluetoothDeviceListHighRisk.observe(viewLifecycleOwner) {
bluetoothDeviceAdapterHighRisk.submitList(it)
- // Ugly workaround because i don't know why this adapter only displays items after a screen wake up...
bluetoothDeviceAdapterHighRisk.notifyDataSetChanged()
}
scanViewModel.bluetoothDeviceListLowRisk.observe(viewLifecycleOwner) {
bluetoothDeviceAdapterLowRisk.submitList(it)
- // Ugly workaround because i don't know why this adapter only displays items after a screen wake up...
bluetoothDeviceAdapterLowRisk.notifyDataSetChanged()
}
@@ -62,6 +60,7 @@ class ScanFragment : Fragment() {
}
}
+ // TODO: Sorting is currently not working
scanViewModel.sortingOrder.observe(viewLifecycleOwner) {
val bluetoothDeviceListHighRiskValue = scanViewModel.bluetoothDeviceListHighRisk.value ?: return@observe
val bluetoothDeviceListLowRiskValue = scanViewModel.bluetoothDeviceListLowRisk.value ?: return@observe
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 0b9abd4a..35c12203 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
@@ -106,12 +106,15 @@ class ScanViewModel @Inject constructor(
bluetoothDeviceListLowRiskValue.add(scanResult)
}
- // TODO: is sorting necessary long term?
- sortResults(bluetoothDeviceListHighRiskValue)
- sortResults(bluetoothDeviceListLowRiskValue)
+ val sortedHighRiskList = ArrayList(bluetoothDeviceListHighRiskValue)
+ val sortedLowRiskList = ArrayList(bluetoothDeviceListLowRiskValue)
+
+ sortResults(sortedHighRiskList)
+ sortResults(sortedLowRiskList)
+
+ bluetoothDeviceListHighRisk.postValue(sortedHighRiskList)
+ bluetoothDeviceListLowRisk.postValue(sortedLowRiskList)
- bluetoothDeviceListHighRisk.postValue(bluetoothDeviceListHighRiskValue)
- bluetoothDeviceListLowRisk.postValue(bluetoothDeviceListLowRiskValue)
Timber.d("Adding scan result ${scanResult.device.address} with unique identifier $uniqueIdentifier")
Timber.d(
"status bytes: ${
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 a15469fb..a8a33440 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
@@ -21,8 +21,7 @@ import timber.log.Timber
* Not to be used for Background scanning. This is handled in the `ScanBluetoothWorker`
*/
object BLEScanner {
-
- var bluetoothManager: BluetoothManager? = null
+ private var bluetoothManager: BluetoothManager? = null
var callbacks = ArrayList()
var isScanning = false
private var lastLocation: Location? = null
@@ -39,6 +38,12 @@ object BLEScanner {
if(this.bluetoothManager != null && isScanning) { return true }
this.bluetoothManager = appContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
+ val bluetoothAdapter = this.bluetoothManager?.adapter
+
+ if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled) {
+ Timber.d("Bluetooth is not enabled.")
+ return false
+ }
val scanSettings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
From f607a7ea17add2a7f0d186c3c7b739909ea25d1d Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 22 Jan 2024 12:17:40 +0100
Subject: [PATCH 067/154] some more optimizations to the processing of manual
scan items, Device Detection with more error checks, temporarily remove
Sorting Options for Manual Scan
---
.../database/models/device/BaseDevice.kt | 26 +--
.../database/models/device/DeviceManager.kt | 45 ++---
.../models/device/types/SamsungDevice.kt | 17 +-
.../ui/scan/ScanFragment.kt | 88 +++++-----
.../ui/scan/ScanViewModel.kt | 30 ++--
app/src/main/res/layout/fragment_scan.xml | 162 +++++++++---------
6 files changed, 181 insertions(+), 187 deletions(-)
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 26e8c894..c548e52d 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
@@ -121,18 +121,18 @@ data class BaseDevice(
companion object {
fun getDeviceName(scanResult: ScanResult): String? {
return when (DeviceManager.getDeviceType(scanResult)) {
- DeviceType.GALAXY_SMART_TAG_PLUS -> null
+ DeviceType.GALAXY_SMART_TAG_PLUS,
DeviceType.GALAXY_SMART_TAG -> null
else -> scanResult.scanRecord?.deviceName
}
}
- fun getPublicKey(scanResult: ScanResult): String{
+ fun getPublicKey(scanResult: ScanResult): String {
return when (DeviceManager.getDeviceType(scanResult)) {
- DeviceType.SAMSUNG -> SamsungDevice.getPublicKey(scanResult)
- DeviceType.GALAXY_SMART_TAG -> SamsungDevice.getPublicKey(scanResult)
+ DeviceType.SAMSUNG,
+ DeviceType.GALAXY_SMART_TAG,
DeviceType.GALAXY_SMART_TAG_PLUS -> SamsungDevice.getPublicKey(scanResult)
- else -> scanResult.device.address
+ else -> scanResult.device.address // Default case to handle unknown types
}
}
@@ -140,12 +140,12 @@ data class BaseDevice(
return when (DeviceManager.getDeviceType(scanResult)) {
DeviceType.TILE -> Tile.getConnectionState(scanResult)
DeviceType.CHIPOLO -> Chipolo.getConnectionState(scanResult)
- DeviceType.SAMSUNG -> SamsungDevice.getConnectionState(scanResult)
- DeviceType.GALAXY_SMART_TAG -> SamsungDevice.getConnectionState(scanResult)
+ DeviceType.SAMSUNG,
+ DeviceType.GALAXY_SMART_TAG,
DeviceType.GALAXY_SMART_TAG_PLUS -> SamsungDevice.getConnectionState(scanResult)
- DeviceType.AIRPODS -> AppleDevice.getConnectionState(scanResult)
- DeviceType.FIND_MY -> AppleDevice.getConnectionState(scanResult)
- DeviceType.AIRTAG -> AppleDevice.getConnectionState(scanResult)
+ DeviceType.AIRPODS,
+ DeviceType.FIND_MY,
+ DeviceType.AIRTAG,
DeviceType.APPLE -> AppleDevice.getConnectionState(scanResult)
else -> ConnectionState.UNKNOWN
}
@@ -153,10 +153,10 @@ data class BaseDevice(
fun getBatteryState(scanResult: ScanResult): BatteryState {
return when (DeviceManager.getDeviceType(scanResult)) {
- DeviceType.GALAXY_SMART_TAG -> SamsungDevice.getBatteryState(scanResult)
+ DeviceType.GALAXY_SMART_TAG,
DeviceType.GALAXY_SMART_TAG_PLUS -> SamsungDevice.getBatteryState(scanResult)
- DeviceType.FIND_MY -> AirTag.getBatteryState(scanResult)
- DeviceType.AIRTAG -> AirTag.getBatteryState(scanResult)
+ DeviceType.FIND_MY,
+ DeviceType.AIRTAG,
DeviceType.AIRPODS -> AirTag.getBatteryState(scanResult)
else -> BatteryState.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 4bdb8f40..f92d5059 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
@@ -17,37 +17,28 @@ object DeviceManager {
fun getDeviceType(scanResult: ScanResult): DeviceType {
Timber.d("Checking device type for ${scanResult.device.address}")
- 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
+ scanResult.scanRecord?.let { scanRecord ->
+ scanRecord.getManufacturerSpecificData(0x004c)?.let { manufacturerData ->
+ if (manufacturerData.size >= 3) { // Ensure array size is sufficient
+ val statusByte: Byte = manufacturerData[2]
+ val deviceTypeInt = (statusByte.and(0x30).toInt() shr 4)
+
+ for (device in appleDevices) {
+ if (device.statusByteDeviceType == deviceTypeInt.toUInt()) {
+ return device.deviceType
+ }
+ }
}
}
- return deviceTypeCheck ?: Unknown.deviceType
- }else if (services != null) {
- //Check if this device is a Tile
- if (services.contains(Tile.offlineFindingServiceUUID)) {
- return Tile.deviceType
- }
- else if(services.contains(Chipolo.offlineFindingServiceUUID)){
- return Chipolo.deviceType
- }
- else if(services.contains(SmartTag.offlineFindingServiceUUID)){
- return SamsungDevice.getSamsungDeviceType(scanResult)
+ scanRecord.serviceUuids?.let { services ->
+ when {
+ services.contains(Tile.offlineFindingServiceUUID) -> return Tile.deviceType
+ services.contains(Chipolo.offlineFindingServiceUUID) -> return Chipolo.deviceType
+ services.contains(SmartTag.offlineFindingServiceUUID) -> return SamsungDevice.getSamsungDeviceType(scanResult)
+ else -> return Unknown.deviceType
+ }
}
-
}
return Unknown.deviceType
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SamsungDevice.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SamsungDevice.kt
index 1fe6aefa..a19840ff 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SamsungDevice.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SamsungDevice.kt
@@ -105,16 +105,19 @@ open class SamsungDevice(open val id: Int) : Device(){
return BatteryState.UNKNOWN
}
- override fun getPublicKey(scanResult: ScanResult): String{
- val serviceData = scanResult.scanRecord?.getServiceData(SmartTag.offlineFindingServiceUUID)
+ override fun getPublicKey(scanResult: ScanResult): String {
+ try {
+ val serviceData = scanResult.scanRecord?.getServiceData(SmartTag.offlineFindingServiceUUID)
- fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
+ fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
- return if (serviceData == null || serviceData.size < 12) {
- scanResult.device.address
- } else {
- byteArrayOf(serviceData[4], serviceData[5], serviceData[6], serviceData[7], serviceData[8], serviceData[9], serviceData[10], serviceData[11]).toHexString()
+ if (serviceData != null && serviceData.size >= 12) {
+ return byteArrayOf(serviceData[4], serviceData[5], serviceData[6], serviceData[7], serviceData[8], serviceData[9], serviceData[10], serviceData[11]).toHexString()
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "Error getting public key")
}
+ return scanResult.device.address
}
fun getSamsungDeviceType(scanResult: ScanResult): DeviceType{
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
index f305256b..0f3f6969 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
@@ -60,33 +60,33 @@ class ScanFragment : Fragment() {
}
}
- // TODO: Sorting is currently not working
- scanViewModel.sortingOrder.observe(viewLifecycleOwner) {
- val bluetoothDeviceListHighRiskValue = scanViewModel.bluetoothDeviceListHighRisk.value ?: return@observe
- val bluetoothDeviceListLowRiskValue = scanViewModel.bluetoothDeviceListLowRisk.value ?: return@observe
-
- scanViewModel.sortResults(bluetoothDeviceListHighRiskValue)
- scanViewModel.sortResults(bluetoothDeviceListLowRiskValue)
-
- scanViewModel.bluetoothDeviceListHighRisk.postValue(bluetoothDeviceListHighRiskValue)
- scanViewModel.bluetoothDeviceListLowRisk.postValue(bluetoothDeviceListLowRiskValue)
-
- if (view != null) {
- val sortBySignalStrength = requireView().findViewById(R.id.sort_option_signal_strength)
- val sortByDetectionOrder = requireView().findViewById(R.id.sort_option_order_detection)
- val sortByAddress = requireView().findViewById(R.id.sort_option_address)
-
- val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
-
- when(it) {
- SortingOrder.SIGNAL_STRENGTH -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
- SortingOrder.DETECTION_ORDER -> scanViewModel.changeColorOf(sortOptions, sortByDetectionOrder)
- SortingOrder.ADDRESS -> scanViewModel.changeColorOf(sortOptions, sortByAddress)
- else -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
- }
- }
-
- }
+ // TODO: Sorting order does not work right now
+// scanViewModel.sortingOrder.observe(viewLifecycleOwner) {
+// val bluetoothDeviceListHighRiskValue = scanViewModel.bluetoothDeviceListHighRisk.value ?: return@observe
+// val bluetoothDeviceListLowRiskValue = scanViewModel.bluetoothDeviceListLowRisk.value ?: return@observe
+//
+// scanViewModel.sortResults(bluetoothDeviceListHighRiskValue)
+// scanViewModel.sortResults(bluetoothDeviceListLowRiskValue)
+//
+// scanViewModel.bluetoothDeviceListHighRisk.postValue(ArrayList(bluetoothDeviceListHighRiskValue))
+// scanViewModel.bluetoothDeviceListLowRisk.postValue(ArrayList(bluetoothDeviceListLowRiskValue))
+//
+// if (view != null) {
+// val sortBySignalStrength = requireView().findViewById(R.id.sort_option_signal_strength)
+// val sortByDetectionOrder = requireView().findViewById(R.id.sort_option_order_detection)
+// val sortByAddress = requireView().findViewById(R.id.sort_option_address)
+//
+// val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
+//
+// when(it) {
+// SortingOrder.SIGNAL_STRENGTH -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
+// SortingOrder.DETECTION_ORDER -> scanViewModel.changeColorOf(sortOptions, sortByDetectionOrder)
+// SortingOrder.ADDRESS -> scanViewModel.changeColorOf(sortOptions, sortByAddress)
+// else -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
+// }
+// }
+//
+// }
return binding.root
}
@@ -110,23 +110,23 @@ class ScanFragment : Fragment() {
}
}
- val sortBySignalStrength = view.findViewById(R.id.sort_option_signal_strength)
- val sortByDetectionOrder = view.findViewById(R.id.sort_option_order_detection)
- val sortByAddress = view.findViewById(R.id.sort_option_address)
-
- val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
-
- scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
-
- sortBySignalStrength.setOnClickListener {
- scanViewModel.sortingOrder.postValue(SortingOrder.SIGNAL_STRENGTH)
- }
- sortByDetectionOrder.setOnClickListener {
- scanViewModel.sortingOrder.postValue(SortingOrder.DETECTION_ORDER)
- }
- sortByAddress.setOnClickListener {
- scanViewModel.sortingOrder.postValue(SortingOrder.ADDRESS)
- }
+// val sortBySignalStrength = view.findViewById(R.id.sort_option_signal_strength)
+// val sortByDetectionOrder = view.findViewById(R.id.sort_option_order_detection)
+// val sortByAddress = view.findViewById(R.id.sort_option_address)
+//
+// val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
+//
+// scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
+//
+// sortBySignalStrength.setOnClickListener {
+// scanViewModel.sortingOrder.postValue(SortingOrder.SIGNAL_STRENGTH)
+// }
+// sortByDetectionOrder.setOnClickListener {
+// scanViewModel.sortingOrder.postValue(SortingOrder.DETECTION_ORDER)
+// }
+// sortByAddress.setOnClickListener {
+// scanViewModel.sortingOrder.postValue(SortingOrder.ADDRESS)
+// }
}
override fun onStart() {
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 35c12203..39bd3a37 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
@@ -106,14 +106,14 @@ class ScanViewModel @Inject constructor(
bluetoothDeviceListLowRiskValue.add(scanResult)
}
- val sortedHighRiskList = ArrayList(bluetoothDeviceListHighRiskValue)
- val sortedLowRiskList = ArrayList(bluetoothDeviceListLowRiskValue)
+// val sortedHighRiskList = ArrayList(bluetoothDeviceListHighRiskValue)
+// val sortedLowRiskList = ArrayList(bluetoothDeviceListLowRiskValue)
+//
+// sortResults(sortedHighRiskList)
+// sortResults(sortedLowRiskList)
- sortResults(sortedHighRiskList)
- sortResults(sortedLowRiskList)
-
- bluetoothDeviceListHighRisk.postValue(sortedHighRiskList)
- bluetoothDeviceListLowRisk.postValue(sortedLowRiskList)
+ bluetoothDeviceListHighRisk.postValue(ArrayList(bluetoothDeviceListHighRiskValue))
+ bluetoothDeviceListLowRisk.postValue(ArrayList(bluetoothDeviceListLowRiskValue))
Timber.d("Adding scan result ${scanResult.device.address} with unique identifier $uniqueIdentifier")
Timber.d(
@@ -125,14 +125,14 @@ class ScanViewModel @Inject constructor(
Timber.d("Device list (Low Risk): ${bluetoothDeviceListLowRisk.value?.count()}")
}
- fun sortResults(bluetoothDeviceListValue: MutableList) {
- when(sortingOrder.value) {
- SortingOrder.SIGNAL_STRENGTH -> bluetoothDeviceListValue.sortByDescending { it.rssi }
- SortingOrder.DETECTION_ORDER -> bluetoothDeviceListValue.sortByDescending { it.timestampNanos }
- SortingOrder.ADDRESS -> bluetoothDeviceListValue.sortBy { it.device.address }
- else -> bluetoothDeviceListValue.sortByDescending { it.rssi }
- }
- }
+// fun sortResults(bluetoothDeviceListValue: MutableList) {
+// when(sortingOrder.value) {
+// SortingOrder.SIGNAL_STRENGTH -> bluetoothDeviceListValue.sortByDescending { it.rssi }
+// SortingOrder.DETECTION_ORDER -> bluetoothDeviceListValue.sortByDescending { it.timestampNanos }
+// SortingOrder.ADDRESS -> bluetoothDeviceListValue.sortBy { it.device.address }
+// else -> bluetoothDeviceListValue.sortByDescending { it.rssi }
+// }
+// }
fun changeColorOf(sortOptions: List, sortOption: TextView) {
val theme = Utility.getSelectedTheme()
diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml
index 382b6617..4cdc4483 100644
--- a/app/src/main/res/layout/fragment_scan.xml
+++ b/app/src/main/res/layout/fragment_scan.xml
@@ -25,86 +25,86 @@
android:layout_height="match_parent"
tools:context=".ui.scan.ScanFragment">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toTopOf="parent" />
Date: Mon, 15 Jan 2024 14:36:21 +0100
Subject: [PATCH 068/154] A New notification for a device will only be sent if
the device meets all the criteria for a notification again only considering
events after the last notification.
---
.../detection/TrackingDetectorWorker.kt | 2 +-
.../util/risk/RiskLevelEvaluator.kt | 34 ++++++++++++++-----
2 files changed, 27 insertions(+), 9 deletions(-)
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 1a978ba3..909d1963 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
@@ -51,7 +51,7 @@ class TrackingDetectorWorker @AssistedInject constructor(
val device = deviceRepository.getDevice(mapEntry.key) ?: return@forEach
val useLocation = SharedPrefs.useLocationInTrackingDetection
- if (RiskLevelEvaluator.checkRiskLevelForDevice(device, useLocation) != RiskLevel.LOW && checkLastNotification(device)) {
+ if (RiskLevelEvaluator.checkRiskLevelForDevice(device, useLocation, true) != RiskLevel.LOW && checkLastNotification(device)) {
// Send Notification
Timber.d("Conditions for device ${device.address} being a tracking device are true... Sending Notification!")
notificationService.sendTrackingNotification(device)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
index 081ea4d0..f428f7f8 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
@@ -92,7 +92,7 @@ class RiskLevelEvaluator(
private const val RELEVANT_DAYS_NOTIFICATIONS: Long = 5 // After MEDIUM risk notifications in the last x days (for a single device) change risk level to HIGH
private const val NUMBER_OF_BEACONS_BEFORE_ALARM: Int = 3 // Number of total beacons before notification is created
private const val MAX_ACCURACY_FOR_LOCATIONS: Float = 100.0F // Minimum Location accuracy for high risk
- const val HOURS_AT_LEAST_UNTIL_NEXT_NOTIFICATION: Long = 8 // Minimum time difference until next notification
+ const val HOURS_AT_LEAST_UNTIL_NEXT_NOTIFICATION: Long = 1 // Minimum time difference until next notification
const val MAX_NUMBER_MEDIUM_RISK: Long = 3 // Maximum number of devices with MEDIUM risk until the total risk level is set to high
val relevantTrackingDateDefault: LocalDateTime = LocalDateTime.now().minusHours(
RELEVANT_HOURS) // Fallback Option, if possible use getRelevantTrackingDate() Function
@@ -112,7 +112,18 @@ class RiskLevelEvaluator(
getMinutesAtLeastTrackedBeforeAlarm()
)
- private fun getRelevantTrackingDate(relevantDays: Long): LocalDateTime = LocalDateTime.now().minusDays(relevantDays)
+ private fun getRelevantTrackingDate(
+ relevantHours: Long,
+ lastNotificationSent: LocalDateTime?,
+ valueForNotification: Boolean
+ ): LocalDateTime {
+ val relevantTrackingDateRaw = LocalDateTime.now().minusHours(relevantHours)
+ return if (!valueForNotification || lastNotificationSent == null || relevantTrackingDateRaw.isAfter(lastNotificationSent)) {
+ relevantTrackingDateRaw
+ } else {
+ lastNotificationSent
+ }
+ }
fun getMinutesAtLeastTrackedBeforeAlarm(): Long {
return when (SharedPrefs.riskSensitivity) {
@@ -141,18 +152,25 @@ class RiskLevelEvaluator(
}
}
- // Checks if BaseDevice is a tracking device
- // Goes through all the criteria
- fun checkRiskLevelForDevice(device: BaseDevice, useLocation: Boolean): RiskLevel {
+ /**
+ * Checks if BaseDevice is a tracking device, Checks if all the criteria for a tracking device are met
+ * @param device the device to check
+ * @param useLocation if the location should be used for the check
+ * @param valueForNotification if the check is for a notification, Standard: For Risk Calculation for the dashboard. Notifications are only thrown if the criteria is met again after the last notification, therefore a different calculation method is required.
+ * @return the risk level of the device under the given circumstances
+ */
+ fun checkRiskLevelForDevice(device: BaseDevice, useLocation: Boolean, valueForNotification: Boolean = false): RiskLevel {
Timber.d("Checking Risk Level for Device: ${device.address}")
val beaconRepository = ATTrackingDetectionApplication.getCurrentApp()?.beaconRepository ?: return RiskLevel.LOW
+ val lastNotificationSent = device.lastNotificationSent
// Not ignored
// Tracker has been seen long enough
- if (!device.ignore && device.firstDiscovery <= getAtLeastTrackedSince()) {
- val relevantDays: Long = device.deviceType?.getNumberOfHoursToBeConsideredForTrackingDetection() ?: RELEVANT_HOURS
- val relevantTrackingDate = getRelevantTrackingDate(relevantDays)
+ if (!device.ignore && device.firstDiscovery <= getAtLeastTrackedSince() &&
+ (lastNotificationSent == null || device.lastSeen.isAfter(lastNotificationSent))) {
+ val relevantHours: Long = device.deviceType?.getNumberOfHoursToBeConsideredForTrackingDetection() ?: RELEVANT_HOURS
+ val relevantTrackingDate = getRelevantTrackingDate(relevantHours, lastNotificationSent, valueForNotification)
val numberOfBeacons = beaconRepository.getNumberOfBeaconsAddress(device.address, relevantTrackingDate)
// Detected at least 3 Times
From 60ffc7a90df0efe20c778ca3db5d2d95091ecb94 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 25 Jan 2024 13:12:20 +0100
Subject: [PATCH 069/154] upgrade Gradle and some dependecies
---
app/build.gradle | 10 +++++-----
.../at_tracking_detection/ui/scan/ScanViewModel.kt | 3 +--
build.gradle | 2 +-
3 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index b67db716..dea37190 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -96,11 +96,11 @@ dependencies {
implementation 'androidx.work:work-runtime-ktx:2.8.1'
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'com.google.android.material:material:1.10.0'
+ implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
- implementation 'androidx.navigation:navigation-fragment-ktx:2.7.5'
- implementation 'androidx.navigation:navigation-ui-ktx:2.7.5'
+ implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6'
+ implementation 'androidx.navigation:navigation-ui-ktx:2.7.6'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
implementation 'androidx.preference:preference-ktx:1.2.1'
@@ -157,7 +157,7 @@ dependencies {
//Compose
// Integration with activities
- implementation 'androidx.activity:activity-compose:1.8.1'
+ implementation 'androidx.activity:activity-compose:1.8.2'
// Compose Material Design
implementation 'androidx.compose.material:material:1.5.4'
// Animations
@@ -165,7 +165,7 @@ dependencies {
// Tooling support (Previews, etc.)
implementation 'androidx.compose.ui:ui-tooling:1.5.4'
// Integration with ViewModels
- implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
// UI Tests
androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.5.4'
// When using a MDC theme
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 39bd3a37..cfb912aa 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
@@ -33,8 +33,7 @@ class ScanViewModel @Inject constructor(
private val beaconRepository: BeaconRepository,
private val locationProvider: LocationProvider,
) : ViewModel() {
-
- val bluetoothDeviceListHighRisk = MutableLiveData>()
+ val bluetoothDeviceListHighRisk = MutableLiveData>() // TODO: Problem needs to be immutable so that DiffUtil works
val bluetoothDeviceListLowRisk = MutableLiveData>()
diff --git a/build.gradle b/build.gradle
index 3aaf12a8..aa1d8a6e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,7 +13,7 @@ buildscript {
}
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.2.1'
+ classpath 'com.android.tools.build:gradle:8.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0"
From fca93399476e244199bba1c3a51cf22afd53ffcd Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 25 Jan 2024 13:48:20 +0100
Subject: [PATCH 070/154] upgrade Kotlin Version to 1.9.22, more dependencies,
migrate some functions to be compatible with new kotlin version
---
app/build.gradle | 8 ++---
.../database/models/device/types/AirTag.kt | 1 +
.../detection/TrackingDetectorWorker.kt | 2 +-
.../ui/TrackingNotificationActivity.kt | 17 ++++++----
.../util/risk/RiskLevelEvaluator.kt | 34 +++++--------------
build.gradle | 4 +--
6 files changed, 26 insertions(+), 40 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index dea37190..487563e5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -66,7 +66,7 @@ android {
}
composeOptions {
- kotlinCompilerExtensionVersion "1.4.8"
+ kotlinCompilerExtensionVersion "1.5.8"
}
@@ -101,8 +101,8 @@ dependencies {
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.6'
- implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
@@ -134,7 +134,7 @@ dependencies {
kapt "com.google.dagger:hilt-compiler:$hilt_version"
- kapt 'androidx.hilt:hilt-compiler:1.0.0'
+ kapt 'androidx.hilt:hilt-compiler:1.1.0'
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
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 aa1a6505..ea815acb 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
@@ -96,6 +96,7 @@ class AirTag(val id: Int) : Device(), Connectable {
super.onCharacteristicWrite(gatt, characteristic, status)
}
+ @Deprecated("Deprecated in Java")
override fun onCharacteristicRead(
gatt: BluetoothGatt?,
characteristic: BluetoothGattCharacteristic?,
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 909d1963..1a978ba3 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
@@ -51,7 +51,7 @@ class TrackingDetectorWorker @AssistedInject constructor(
val device = deviceRepository.getDevice(mapEntry.key) ?: return@forEach
val useLocation = SharedPrefs.useLocationInTrackingDetection
- if (RiskLevelEvaluator.checkRiskLevelForDevice(device, useLocation, true) != RiskLevel.LOW && checkLastNotification(device)) {
+ if (RiskLevelEvaluator.checkRiskLevelForDevice(device, useLocation) != RiskLevel.LOW && checkLastNotification(device)) {
// Send Notification
Timber.d("Conditions for device ${device.address} being a tracking device are true... Sending Notification!")
notificationService.sendTrackingNotification(device)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/TrackingNotificationActivity.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/TrackingNotificationActivity.kt
index 5e0bc7f5..c8898412 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/TrackingNotificationActivity.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/TrackingNotificationActivity.kt
@@ -2,6 +2,7 @@ package de.seemoo.at_tracking_detection.ui
import android.content.Intent
import android.os.Bundle
+import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
@@ -18,12 +19,13 @@ class TrackingNotificationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tracking)
- val navHostFragment =
- supportFragmentManager.findFragmentById(R.id.tracking_host_fragment) as NavHostFragment
+ val navHostFragment = supportFragmentManager.findFragmentById(R.id.tracking_host_fragment) as NavHostFragment
navController = navHostFragment.navController
+
val deviceAddress = intent.getStringExtra("deviceAddress")
val notificationId = intent.getIntExtra("notificationId", -1)
Timber.d("Tracking Activity with device $deviceAddress and notification $notificationId started!")
+
if (deviceAddress == null) {
Timber.e("Device address is needed! Going home...")
this.onSupportNavigateUp()
@@ -31,17 +33,18 @@ class TrackingNotificationActivity : AppCompatActivity() {
val args = TrackingFragmentArgs(deviceAddress, notificationId).toBundle()
navController.setGraph(R.navigation.tracking_navigation, args)
}
- }
- override fun onBackPressed() {
- onNavigateUp()
+ onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ onNavigateUp()
+ }
+ })
}
override fun onSupportNavigateUp(): Boolean {
if (!navController.navigateUp()) {
startActivity(Intent(this, MainActivity::class.java).apply {
- flags =
- Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
})
}
return true
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
index f428f7f8..081ea4d0 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
@@ -92,7 +92,7 @@ class RiskLevelEvaluator(
private const val RELEVANT_DAYS_NOTIFICATIONS: Long = 5 // After MEDIUM risk notifications in the last x days (for a single device) change risk level to HIGH
private const val NUMBER_OF_BEACONS_BEFORE_ALARM: Int = 3 // Number of total beacons before notification is created
private const val MAX_ACCURACY_FOR_LOCATIONS: Float = 100.0F // Minimum Location accuracy for high risk
- const val HOURS_AT_LEAST_UNTIL_NEXT_NOTIFICATION: Long = 1 // Minimum time difference until next notification
+ const val HOURS_AT_LEAST_UNTIL_NEXT_NOTIFICATION: Long = 8 // Minimum time difference until next notification
const val MAX_NUMBER_MEDIUM_RISK: Long = 3 // Maximum number of devices with MEDIUM risk until the total risk level is set to high
val relevantTrackingDateDefault: LocalDateTime = LocalDateTime.now().minusHours(
RELEVANT_HOURS) // Fallback Option, if possible use getRelevantTrackingDate() Function
@@ -112,18 +112,7 @@ class RiskLevelEvaluator(
getMinutesAtLeastTrackedBeforeAlarm()
)
- private fun getRelevantTrackingDate(
- relevantHours: Long,
- lastNotificationSent: LocalDateTime?,
- valueForNotification: Boolean
- ): LocalDateTime {
- val relevantTrackingDateRaw = LocalDateTime.now().minusHours(relevantHours)
- return if (!valueForNotification || lastNotificationSent == null || relevantTrackingDateRaw.isAfter(lastNotificationSent)) {
- relevantTrackingDateRaw
- } else {
- lastNotificationSent
- }
- }
+ private fun getRelevantTrackingDate(relevantDays: Long): LocalDateTime = LocalDateTime.now().minusDays(relevantDays)
fun getMinutesAtLeastTrackedBeforeAlarm(): Long {
return when (SharedPrefs.riskSensitivity) {
@@ -152,25 +141,18 @@ class RiskLevelEvaluator(
}
}
- /**
- * Checks if BaseDevice is a tracking device, Checks if all the criteria for a tracking device are met
- * @param device the device to check
- * @param useLocation if the location should be used for the check
- * @param valueForNotification if the check is for a notification, Standard: For Risk Calculation for the dashboard. Notifications are only thrown if the criteria is met again after the last notification, therefore a different calculation method is required.
- * @return the risk level of the device under the given circumstances
- */
- fun checkRiskLevelForDevice(device: BaseDevice, useLocation: Boolean, valueForNotification: Boolean = false): RiskLevel {
+ // Checks if BaseDevice is a tracking device
+ // Goes through all the criteria
+ fun checkRiskLevelForDevice(device: BaseDevice, useLocation: Boolean): RiskLevel {
Timber.d("Checking Risk Level for Device: ${device.address}")
val beaconRepository = ATTrackingDetectionApplication.getCurrentApp()?.beaconRepository ?: return RiskLevel.LOW
- val lastNotificationSent = device.lastNotificationSent
// Not ignored
// Tracker has been seen long enough
- if (!device.ignore && device.firstDiscovery <= getAtLeastTrackedSince() &&
- (lastNotificationSent == null || device.lastSeen.isAfter(lastNotificationSent))) {
- val relevantHours: Long = device.deviceType?.getNumberOfHoursToBeConsideredForTrackingDetection() ?: RELEVANT_HOURS
- val relevantTrackingDate = getRelevantTrackingDate(relevantHours, lastNotificationSent, valueForNotification)
+ if (!device.ignore && device.firstDiscovery <= getAtLeastTrackedSince()) {
+ val relevantDays: Long = device.deviceType?.getNumberOfHoursToBeConsideredForTrackingDetection() ?: RELEVANT_HOURS
+ val relevantTrackingDate = getRelevantTrackingDate(relevantDays)
val numberOfBeacons = beaconRepository.getNumberOfBeaconsAddress(device.address, relevantTrackingDate)
// Detected at least 3 Times
diff --git a/build.gradle b/build.gradle
index aa1d8a6e..fa6033ad 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.8.22'
+ ext.kotlin_version = '1.9.22'
ext.hilt_version = '2.48'
ext.room_version = '2.6.1'
ext.compose_version = '1.1.1'
@@ -16,7 +16,7 @@ buildscript {
classpath 'com.android.tools.build:gradle:8.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
- classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0"
+ classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.6"
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries_version"
// NOTE: Do not place your application dependencies here; they belong
From a2a64cd50b57117af858fae74ca62bce1a895c9f Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 31 Jan 2024 11:40:57 +0100
Subject: [PATCH 071/154] reintroduce Scan Sorting again
---
.../ui/scan/ScanFragment.kt | 88 +++++-----
.../ui/scan/ScanViewModel.kt | 26 +--
app/src/main/res/layout/fragment_scan.xml | 162 +++++++++---------
3 files changed, 138 insertions(+), 138 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
index 0f3f6969..e8203afc 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
@@ -42,6 +42,7 @@ class ScanFragment : Fragment() {
binding.lifecycleOwner = viewLifecycleOwner
binding.vm = scanViewModel
+ // TODO: notifyDataSetChanged is very inefficient. DiffUtil does not work properly for some reason
scanViewModel.bluetoothDeviceListHighRisk.observe(viewLifecycleOwner) {
bluetoothDeviceAdapterHighRisk.submitList(it)
bluetoothDeviceAdapterHighRisk.notifyDataSetChanged()
@@ -60,33 +61,32 @@ class ScanFragment : Fragment() {
}
}
- // TODO: Sorting order does not work right now
-// scanViewModel.sortingOrder.observe(viewLifecycleOwner) {
-// val bluetoothDeviceListHighRiskValue = scanViewModel.bluetoothDeviceListHighRisk.value ?: return@observe
-// val bluetoothDeviceListLowRiskValue = scanViewModel.bluetoothDeviceListLowRisk.value ?: return@observe
-//
-// scanViewModel.sortResults(bluetoothDeviceListHighRiskValue)
-// scanViewModel.sortResults(bluetoothDeviceListLowRiskValue)
-//
-// scanViewModel.bluetoothDeviceListHighRisk.postValue(ArrayList(bluetoothDeviceListHighRiskValue))
-// scanViewModel.bluetoothDeviceListLowRisk.postValue(ArrayList(bluetoothDeviceListLowRiskValue))
-//
-// if (view != null) {
-// val sortBySignalStrength = requireView().findViewById(R.id.sort_option_signal_strength)
-// val sortByDetectionOrder = requireView().findViewById(R.id.sort_option_order_detection)
-// val sortByAddress = requireView().findViewById(R.id.sort_option_address)
-//
-// val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
-//
-// when(it) {
-// SortingOrder.SIGNAL_STRENGTH -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
-// SortingOrder.DETECTION_ORDER -> scanViewModel.changeColorOf(sortOptions, sortByDetectionOrder)
-// SortingOrder.ADDRESS -> scanViewModel.changeColorOf(sortOptions, sortByAddress)
-// else -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
-// }
-// }
-//
-// }
+ scanViewModel.sortingOrder.observe(viewLifecycleOwner) {
+ val bluetoothDeviceListHighRiskValue = scanViewModel.bluetoothDeviceListHighRisk.value ?: return@observe
+ val bluetoothDeviceListLowRiskValue = scanViewModel.bluetoothDeviceListLowRisk.value ?: return@observe
+
+ scanViewModel.sortResults(bluetoothDeviceListHighRiskValue)
+ scanViewModel.sortResults(bluetoothDeviceListLowRiskValue)
+
+ scanViewModel.bluetoothDeviceListHighRisk.postValue(ArrayList(bluetoothDeviceListHighRiskValue))
+ scanViewModel.bluetoothDeviceListLowRisk.postValue(ArrayList(bluetoothDeviceListLowRiskValue))
+
+ if (view != null) {
+ val sortBySignalStrength = requireView().findViewById(R.id.sort_option_signal_strength)
+ val sortByDetectionOrder = requireView().findViewById(R.id.sort_option_order_detection)
+ val sortByAddress = requireView().findViewById(R.id.sort_option_address)
+
+ val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
+
+ when(it) {
+ SortingOrder.SIGNAL_STRENGTH -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
+ SortingOrder.DETECTION_ORDER -> scanViewModel.changeColorOf(sortOptions, sortByDetectionOrder)
+ SortingOrder.ADDRESS -> scanViewModel.changeColorOf(sortOptions, sortByAddress)
+ else -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
+ }
+ }
+
+ }
return binding.root
}
@@ -110,23 +110,23 @@ class ScanFragment : Fragment() {
}
}
-// val sortBySignalStrength = view.findViewById(R.id.sort_option_signal_strength)
-// val sortByDetectionOrder = view.findViewById(R.id.sort_option_order_detection)
-// val sortByAddress = view.findViewById(R.id.sort_option_address)
-//
-// val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
-//
-// scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
-//
-// sortBySignalStrength.setOnClickListener {
-// scanViewModel.sortingOrder.postValue(SortingOrder.SIGNAL_STRENGTH)
-// }
-// sortByDetectionOrder.setOnClickListener {
-// scanViewModel.sortingOrder.postValue(SortingOrder.DETECTION_ORDER)
-// }
-// sortByAddress.setOnClickListener {
-// scanViewModel.sortingOrder.postValue(SortingOrder.ADDRESS)
-// }
+ val sortBySignalStrength = view.findViewById(R.id.sort_option_signal_strength)
+ val sortByDetectionOrder = view.findViewById(R.id.sort_option_order_detection)
+ val sortByAddress = view.findViewById(R.id.sort_option_address)
+
+ val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
+
+ scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
+
+ sortBySignalStrength.setOnClickListener {
+ scanViewModel.sortingOrder.postValue(SortingOrder.SIGNAL_STRENGTH)
+ }
+ sortByDetectionOrder.setOnClickListener {
+ scanViewModel.sortingOrder.postValue(SortingOrder.DETECTION_ORDER)
+ }
+ sortByAddress.setOnClickListener {
+ scanViewModel.sortingOrder.postValue(SortingOrder.ADDRESS)
+ }
}
override fun onStart() {
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 cfb912aa..90fff9f4 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
@@ -105,11 +105,11 @@ class ScanViewModel @Inject constructor(
bluetoothDeviceListLowRiskValue.add(scanResult)
}
-// val sortedHighRiskList = ArrayList(bluetoothDeviceListHighRiskValue)
-// val sortedLowRiskList = ArrayList(bluetoothDeviceListLowRiskValue)
-//
-// sortResults(sortedHighRiskList)
-// sortResults(sortedLowRiskList)
+ val sortedHighRiskList = ArrayList(bluetoothDeviceListHighRiskValue)
+ val sortedLowRiskList = ArrayList(bluetoothDeviceListLowRiskValue)
+
+ sortResults(sortedHighRiskList)
+ sortResults(sortedLowRiskList)
bluetoothDeviceListHighRisk.postValue(ArrayList(bluetoothDeviceListHighRiskValue))
bluetoothDeviceListLowRisk.postValue(ArrayList(bluetoothDeviceListLowRiskValue))
@@ -124,14 +124,14 @@ class ScanViewModel @Inject constructor(
Timber.d("Device list (Low Risk): ${bluetoothDeviceListLowRisk.value?.count()}")
}
-// fun sortResults(bluetoothDeviceListValue: MutableList) {
-// when(sortingOrder.value) {
-// SortingOrder.SIGNAL_STRENGTH -> bluetoothDeviceListValue.sortByDescending { it.rssi }
-// SortingOrder.DETECTION_ORDER -> bluetoothDeviceListValue.sortByDescending { it.timestampNanos }
-// SortingOrder.ADDRESS -> bluetoothDeviceListValue.sortBy { it.device.address }
-// else -> bluetoothDeviceListValue.sortByDescending { it.rssi }
-// }
-// }
+ fun sortResults(bluetoothDeviceListValue: MutableList) {
+ when(sortingOrder.value) {
+ SortingOrder.SIGNAL_STRENGTH -> bluetoothDeviceListValue.sortByDescending { it.rssi }
+ SortingOrder.DETECTION_ORDER -> bluetoothDeviceListValue.sortByDescending { it.timestampNanos }
+ SortingOrder.ADDRESS -> bluetoothDeviceListValue.sortBy { it.device.address }
+ else -> bluetoothDeviceListValue.sortByDescending { it.rssi }
+ }
+ }
fun changeColorOf(sortOptions: List, sortOption: TextView) {
val theme = Utility.getSelectedTheme()
diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml
index 4cdc4483..f8bc5ed2 100644
--- a/app/src/main/res/layout/fragment_scan.xml
+++ b/app/src/main/res/layout/fragment_scan.xml
@@ -25,86 +25,86 @@
android:layout_height="match_parent"
tools:context=".ui.scan.ScanFragment">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/sorting_options" />
Date: Wed, 31 Jan 2024 12:00:27 +0100
Subject: [PATCH 072/154] Detail Scan now shows correct Time since Tracker is
in offline Mode
---
.../ui/scan/ScanDistanceFragment.kt | 13 +++++++++----
app/src/main/res/values-de/strings.xml | 3 ++-
app/src/main/res/values/strings.xml | 3 ++-
3 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
index 7de47943..91407b61 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
@@ -57,7 +57,8 @@ class ScanDistanceFragment : Fragment() {
// viewModel.bluetoothRssi.postValue(it.rssi)
val connectionState = getConnectionState(it)
viewModel.connectionState.postValue(connectionState)
- val connectionStateString = getConnectionStateExplanation(connectionState)
+ val deviceType = DeviceManager.getDeviceType(it)
+ val connectionStateString = getConnectionStateExplanation(connectionState, deviceType)
viewModel.connectionStateString.postValue(connectionStateString)
val batteryState = getBatteryState(it)
@@ -68,7 +69,6 @@ class ScanDistanceFragment : Fragment() {
val displayedConnectionQuality = (connectionQuality * 100).toInt()
viewModel.connectionQuality.postValue(displayedConnectionQuality)
- val deviceType = DeviceManager.getDeviceType(it)
setDeviceType(requireContext(), deviceType)
setBattery(requireContext(), batteryState)
setHeight(connectionQuality)
@@ -172,9 +172,14 @@ class ScanDistanceFragment : Fragment() {
binding.deviceTypeText.text = DeviceType.userReadableName(deviceType)
}
- private fun getConnectionStateExplanation(connectionState: ConnectionState): String {
+ private fun getConnectionStateExplanation(connectionState: ConnectionState, deviceType: DeviceType): String {
return when (connectionState) {
- ConnectionState.OVERMATURE_OFFLINE -> getString(R.string.connection_state_overmature_offline_explanation)
+ ConnectionState.OVERMATURE_OFFLINE -> when(deviceType) {
+ DeviceType.SAMSUNG,
+ DeviceType.GALAXY_SMART_TAG,
+ DeviceType.GALAXY_SMART_TAG_PLUS -> getString(R.string.connection_state_overmature_offline_explanation_samsung)
+ else -> getString(R.string.connection_state_overmature_offline_explanation)
+ }
ConnectionState.CONNECTED -> getString(R.string.connection_state_connected_explanation)
ConnectionState.OFFLINE -> getString(R.string.connection_state_offline_explanation)
ConnectionState.PREMATURE_OFFLINE -> getString(R.string.connection_state_premature_offline_explanation)
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index fea7b304..351ece67 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -257,7 +257,8 @@
Gerät ist vom Besitzer getrennt
Overmature Offline
Premature Offline
- Gerät ist seit mind. 8 Stunden vom Besitzer getrennt
+ Gerät ist seit mind. 15 Minuten vom Besitzer getrennt
+ Gerät ist seit mind. 8 Stunden vom Besitzer getrennt
Verbunden
Gerät ist verbunden mit Besitzer
Gerät war in den letzten 15 Minuten mit Besitzer verbunden
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5af4fc31..c9995f7e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -285,7 +285,8 @@
Offline
Device is separated from its owner
Overmature Offline
- Device sis separated from its owner since at least 8 hours
+ Device is separated from its owner since at least 15 minutes
+ Device is separated from its owner since at least 8 hours
Premature Offline
Connection to owner in the last 15 minutes
Connected
From f5a9b099eb66ae755230437fdf78d2ec0984e0f6 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 7 Feb 2024 13:25:37 +0100
Subject: [PATCH 073/154] add Legend to map
---
.../main/res/layout/fragment_device_map.xml | 47 ++++++++++++++++++-
app/src/main/res/values-de/strings.xml | 2 +
app/src/main/res/values/strings.xml | 2 +
3 files changed, 49 insertions(+), 2 deletions(-)
diff --git a/app/src/main/res/layout/fragment_device_map.xml b/app/src/main/res/layout/fragment_device_map.xml
index a28b3cdc..142c4c31 100644
--- a/app/src/main/res/layout/fragment_device_map.xml
+++ b/app/src/main/res/layout/fragment_device_map.xml
@@ -19,9 +19,13 @@
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="@{vm.hideMap ? View.GONE : View.VISIBLE, default=visible}"
+ bind:layout_constraintBottom_toTopOf="@+id/legendContainer"
+ bind:layout_constraintTop_toTopOf="parent">
+
+
+
+
+
+
+
+
+
Mögliche Tracker
Sichere Tracker
+ Legende
+ Standort an dem potenzieller Tracker gefunden wurde
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c9995f7e..4cc424f5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -363,4 +363,6 @@
Maintainer: %s
Potential Trackers
Safe Trackers
+ Legend
+ Location where a potential tracker was found
From b0a0d6b511efa60d5f57c1aae6b24e372ba37177 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 7 Feb 2024 13:29:51 +0100
Subject: [PATCH 074/154] upgrade some dependencies
---
app/build.gradle | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 487563e5..8fd4577f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -159,15 +159,15 @@ dependencies {
// Integration with activities
implementation 'androidx.activity:activity-compose:1.8.2'
// Compose Material Design
- implementation 'androidx.compose.material:material:1.5.4'
+ implementation 'androidx.compose.material:material:1.6.0'
// Animations
- implementation 'androidx.compose.animation:animation:1.5.4'
+ implementation 'androidx.compose.animation:animation:1.6.0'
// Tooling support (Previews, etc.)
- implementation 'androidx.compose.ui:ui-tooling:1.5.4'
+ implementation 'androidx.compose.ui:ui-tooling:1.6.0'
// Integration with ViewModels
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
// UI Tests
- androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.5.4'
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.0'
// When using a MDC theme
implementation "com.google.android.material:compose-theme-adapter:1.2.1"
From 45031bf72c214d580c17c6d69ef4008b25246d64 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 7 Feb 2024 21:25:04 +0100
Subject: [PATCH 075/154] upgrade some dependencies, modify dependency
structure
---
app/build.gradle | 16 ++++++++--------
build.gradle | 3 ++-
2 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 8fd4577f..c2686be5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -93,14 +93,14 @@ dependencies {
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation 'com.github.bastienpaulfr:Treessence:1.0.0'
- implementation 'androidx.work:work-runtime-ktx:2.8.1'
+ implementation "androidx.work:work-runtime-ktx:$work_version"
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
- implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6'
- implementation 'androidx.navigation:navigation-ui-ktx:2.7.6'
+ 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.7.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
@@ -112,7 +112,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.5'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
- implementation 'androidx.work:work-testing:2.8.1'
+ implementation "androidx.work:work-testing:$work_version"
implementation 'androidx.core:core-ktx:1.12.0'
debugImplementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.5'
@@ -159,15 +159,15 @@ dependencies {
// Integration with activities
implementation 'androidx.activity:activity-compose:1.8.2'
// Compose Material Design
- implementation 'androidx.compose.material:material:1.6.0'
+ implementation 'androidx.compose.material:material:1.6.1'
// Animations
- implementation 'androidx.compose.animation:animation:1.6.0'
+ implementation 'androidx.compose.animation:animation:1.6.1'
// Tooling support (Previews, etc.)
- implementation 'androidx.compose.ui:ui-tooling:1.6.0'
+ implementation 'androidx.compose.ui:ui-tooling:1.6.1'
// Integration with ViewModels
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
// UI Tests
- androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.0'
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.1'
// When using a MDC theme
implementation "com.google.android.material:compose-theme-adapter:1.2.1"
diff --git a/build.gradle b/build.gradle
index fa6033ad..dab908f3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,6 +5,7 @@ buildscript {
ext.room_version = '2.6.1'
ext.compose_version = '1.1.1'
ext.about_libraries_version = '10.8.3'
+ ext.work_version = '2.8.1'
repositories {
google()
mavenCentral()
@@ -16,7 +17,7 @@ buildscript {
classpath 'com.android.tools.build:gradle:8.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
- classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.6"
+ classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7"
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries_version"
// NOTE: Do not place your application dependencies here; they belong
From ea2b700410dec7c25171a8787e82cb5db4674003 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 10 Feb 2024 16:40:47 +0100
Subject: [PATCH 076/154] hide Website URL Button if Website URL could not be
determined
---
.../at_tracking_detection/ui/tracking/TrackingFragment.kt | 6 ++++--
.../at_tracking_detection/ui/tracking/TrackingViewModel.kt | 5 +++--
app/src/main/res/layout/fragment_tracking.xml | 1 +
3 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index a320eb82..35c2cbd0 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -58,7 +58,7 @@ class TrackingFragment : Fragment() {
binding.lifecycleOwner = viewLifecycleOwner
binding.vm = trackingViewModel
val notificationId = safeArgs.notificationId
- // This is called deviceAddress but contains the ID
+ // This is called deviceAddress but contains the ID not necessarily the address
val deviceAddress = safeArgs.deviceAddress
trackingViewModel.notificationId.postValue(notificationId)
trackingViewModel.deviceAddress.postValue(deviceAddress)
@@ -277,7 +277,9 @@ class TrackingFragment : Fragment() {
} else {
Timber.d("Device is ready to connect!")
trackingViewModel.device.observe(viewLifecycleOwner) { baseDevice ->
- it.connect(baseDevice)
+ if (baseDevice != null) {
+ it.connect(baseDevice)
+ }
}
}
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
index bb93e5ac..7960432c 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
@@ -27,7 +27,7 @@ class TrackingViewModel @Inject constructor(
val noLocationsYet = MutableLiveData(true)
- private val manufacturerWebsiteUrl = MutableLiveData("https://www.apple.com/airtag/")
+ val manufacturerWebsiteUrl = MutableLiveData("https://www.apple.com/airtag/")
val error = MutableLiveData(false)
@@ -38,7 +38,7 @@ class TrackingViewModel @Inject constructor(
val soundPlaying = MutableLiveData(false)
val connecting = MutableLiveData(false)
- val device = MutableLiveData()
+ val device = MutableLiveData()
val connectable = MutableLiveData(false)
val canBeIgnored = MutableLiveData(false)
@@ -81,6 +81,7 @@ class TrackingViewModel @Inject constructor(
notification?.let { notificationId.postValue(it.notificationId) }
} else {
noLocationsYet.postValue(true)
+ manufacturerWebsiteUrl.postValue("")
}
}
diff --git a/app/src/main/res/layout/fragment_tracking.xml b/app/src/main/res/layout/fragment_tracking.xml
index ae5ff8fa..5573b90b 100644
--- a/app/src/main/res/layout/fragment_tracking.xml
+++ b/app/src/main/res/layout/fragment_tracking.xml
@@ -228,6 +228,7 @@
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:orientation="vertical"
+ android:visibility="@{vm.manufacturerWebsiteUrl.isEmpty() ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
From e58dd95afc1a77bdcc2b666b5da496c0f1015649 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 13 Feb 2024 15:51:09 +0100
Subject: [PATCH 077/154] fix bug in sorting of manual scan, fix bug in article
rendering in dashboard
---
.../ui/dashboard/DashboardRiskFragment.kt | 4 ++--
.../at_tracking_detection/ui/scan/ScanViewModel.kt | 10 +++++-----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
index 1ca69b9f..5a69ddfd 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardRiskFragment.kt
@@ -110,7 +110,7 @@ class DashboardRiskFragment : Fragment() {
topMargin = 22
}
- if (article.preview_image.isNotEmpty()) { // TODO: Rename when in production to PreviewImage, also in JSON
+ if (!article.preview_image.isNullOrEmpty()) { // TODO: Rename when in production to PreviewImage, also in JSON
val imageURL = getURL(article.preview_image) // TODO: Rename when in production to PreviewImage, also in JSON
context?.let {
Glide.with(it)
@@ -122,7 +122,7 @@ class DashboardRiskFragment : Fragment() {
imageViewPreview.visibility = View.GONE
}
- if (article.filename.isNotEmpty()) {
+ if (!article.filename.isNullOrEmpty()) {
articleCard.setOnClickListener {
val directions: NavDirections =
DashboardRiskFragmentDirections.actionNavigationDashboardToArticleFragment(
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 90fff9f4..cba58063 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
@@ -105,14 +105,14 @@ class ScanViewModel @Inject constructor(
bluetoothDeviceListLowRiskValue.add(scanResult)
}
+ sortResults(bluetoothDeviceListHighRiskValue)
+ sortResults(bluetoothDeviceListLowRiskValue)
+
val sortedHighRiskList = ArrayList(bluetoothDeviceListHighRiskValue)
val sortedLowRiskList = ArrayList(bluetoothDeviceListLowRiskValue)
- sortResults(sortedHighRiskList)
- sortResults(sortedLowRiskList)
-
- bluetoothDeviceListHighRisk.postValue(ArrayList(bluetoothDeviceListHighRiskValue))
- bluetoothDeviceListLowRisk.postValue(ArrayList(bluetoothDeviceListLowRiskValue))
+ bluetoothDeviceListHighRisk.postValue(sortedHighRiskList)
+ bluetoothDeviceListLowRisk.postValue(sortedLowRiskList)
Timber.d("Adding scan result ${scanResult.device.address} with unique identifier $uniqueIdentifier")
Timber.d(
From c1c23ac5d302e4d44ed1ce9f8405c06ca7f0e909 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 14 Feb 2024 11:36:10 +0100
Subject: [PATCH 078/154] adjust RiskLevel Numbers so they are consistent with
the iOS Version
---
.../database/models/device/DeviceContext.kt | 2 +-
.../database/models/device/types/Tile.kt | 6 +++---
.../ui/dashboard/RiskCardViewModel.kt | 6 +++---
.../util/risk/RiskLevelEvaluator.kt | 17 +++++++++--------
4 files changed, 16 insertions(+), 15 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
index 81f3b007..a8a61884 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/DeviceContext.kt
@@ -18,7 +18,7 @@ interface DeviceContext {
val statusByteDeviceType: UInt
val numberOfHoursToBeConsideredForTrackingDetection: Long
- get() = RiskLevelEvaluator.RELEVANT_HOURS
+ get() = RiskLevelEvaluator.RELEVANT_HOURS_TRACKING
val numberOfLocationsToBeConsideredForTrackingDetectionLow: Int
get() = RiskLevelEvaluator.NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW
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 7f3c151e..fafd76e7 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
@@ -45,13 +45,13 @@ class Tile(val id: Int) : Device(){
get() = super.numberOfHoursToBeConsideredForTrackingDetection
override val numberOfLocationsToBeConsideredForTrackingDetectionLow: Int
- get() = super.numberOfLocationsToBeConsideredForTrackingDetectionLow
+ get() = 8
override val numberOfLocationsToBeConsideredForTrackingDetectionMedium: Int
- get() = super.numberOfLocationsToBeConsideredForTrackingDetectionMedium
+ get() = 5
override val numberOfLocationsToBeConsideredForTrackingDetectionHigh: Int
- get() = super.numberOfLocationsToBeConsideredForTrackingDetectionHigh
+ get() = 3
val offlineFindingServiceUUID: ParcelUuid = ParcelUuid.fromString("0000FEED-0000-1000-8000-00805F9B34FB")
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskCardViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskCardViewModel.kt
index 2e521444..345f85d6 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskCardViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskCardViewModel.kt
@@ -86,7 +86,7 @@ class RiskCardViewModel @Inject constructor(
riskColor = ContextCompat.getColor(context, R.color.risk_low)
trackersFoundModel.postValue(RiskRowViewModel(
- context.getString(R.string.no_trackers_found, RiskLevelEvaluator.RELEVANT_HOURS),
+ context.getString(R.string.no_trackers_found, RiskLevelEvaluator.RELEVANT_HOURS_TRACKING),
ContextCompat.getDrawable(context, R.drawable.ic_baseline_location_on_24)!!
))
lastDiscoveryModel.postValue(RiskRowViewModel(
@@ -105,7 +105,7 @@ class RiskCardViewModel @Inject constructor(
context.getString(
R.string.found_x_trackers,
totalAlerts,
- RiskLevelEvaluator.RELEVANT_HOURS
+ RiskLevelEvaluator.RELEVANT_HOURS_TRACKING
),
ContextCompat.getDrawable(context, R.drawable.ic_baseline_location_on_24)!!
))
@@ -127,7 +127,7 @@ class RiskCardViewModel @Inject constructor(
context.getString(
R.string.found_x_trackers,
totalAlerts,
- RiskLevelEvaluator.RELEVANT_HOURS
+ RiskLevelEvaluator.RELEVANT_HOURS_TRACKING
),
ContextCompat.getDrawable(context, R.drawable.ic_baseline_location_on_24)!!
))
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
index 081ea4d0..90418043 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
@@ -79,7 +79,7 @@ class RiskLevelEvaluator(
// How many trackers have been relevant here as tracker
fun getNumberRelevantTrackers(): Int {
- val relevantDate = LocalDateTime.now().minusHours(RELEVANT_HOURS)
+ val relevantDate = LocalDateTime.now().minusHours(RELEVANT_HOURS_TRACKING)
val baseDevices: List = deviceRepository.trackingDevicesNotIgnoredSince(relevantDate)
return baseDevices.count()
@@ -87,7 +87,8 @@ class RiskLevelEvaluator(
companion object {
/** The number of days that we use to calculate the risk **/
- const val RELEVANT_HOURS: Long = 24 // Only consider beacons in the last x hours, default value, can be overwritten in the specific device properties
+ const val RELEVANT_HOURS_TRACKING: Long = 24 // Only consider beacons in the last x hours, default value, can be overwritten in the specific device properties
+ const val RELEVANT_DAYS_RISK_LEVEL: Long = 14
private const val NUMBER_OF_NOTIFICATIONS_FOR_HIGH_RISK: Long = 2 // After x MEDIUM risk notifications (for a single device) change risk level to HIGH
private const val RELEVANT_DAYS_NOTIFICATIONS: Long = 5 // After MEDIUM risk notifications in the last x days (for a single device) change risk level to HIGH
private const val NUMBER_OF_BEACONS_BEFORE_ALARM: Int = 3 // Number of total beacons before notification is created
@@ -95,18 +96,18 @@ class RiskLevelEvaluator(
const val HOURS_AT_LEAST_UNTIL_NEXT_NOTIFICATION: Long = 8 // Minimum time difference until next notification
const val MAX_NUMBER_MEDIUM_RISK: Long = 3 // Maximum number of devices with MEDIUM risk until the total risk level is set to high
val relevantTrackingDateDefault: LocalDateTime = LocalDateTime.now().minusHours(
- RELEVANT_HOURS) // Fallback Option, if possible use getRelevantTrackingDate() Function
+ RELEVANT_HOURS_TRACKING) // Fallback Option, if possible use getRelevantTrackingDate() Function
private val relevantNotificationDate: LocalDateTime = LocalDateTime.now().minusDays(RELEVANT_DAYS_NOTIFICATIONS)
// Default Values: A single tracker gets tracked at least for x minutes until notification is created
private const val MINUTES_AT_LEAST_TRACKED_BEFORE_ALARM_HIGH: Long = 30
private const val MINUTES_AT_LEAST_TRACKED_BEFORE_ALARM_MEDIUM: Long = 60
- private const val MINUTES_AT_LEAST_TRACKED_BEFORE_ALARM_LOW: Long = 90
+ private const val MINUTES_AT_LEAST_TRACKED_BEFORE_ALARM_LOW: Long = 120
- // Default Values: A single tracker gets tracked at least for x minutes until notification is created
+ // Default Values:
const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_HIGH: Int = 3
- const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM: Int = 5
- const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW: Int = 8
+ const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM: Int = 4
+ const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW: Int = 5
private fun getAtLeastTrackedSince(): LocalDateTime = LocalDateTime.now().minusMinutes(
getMinutesAtLeastTrackedBeforeAlarm()
@@ -151,7 +152,7 @@ class RiskLevelEvaluator(
// Not ignored
// Tracker has been seen long enough
if (!device.ignore && device.firstDiscovery <= getAtLeastTrackedSince()) {
- val relevantDays: Long = device.deviceType?.getNumberOfHoursToBeConsideredForTrackingDetection() ?: RELEVANT_HOURS
+ val relevantDays: Long = device.deviceType?.getNumberOfHoursToBeConsideredForTrackingDetection() ?: RELEVANT_HOURS_TRACKING
val relevantTrackingDate = getRelevantTrackingDate(relevantDays)
val numberOfBeacons = beaconRepository.getNumberOfBeaconsAddress(device.address, relevantTrackingDate)
From 0ab67ec6ab4496505cc9eba7062378540732d228 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 18 Feb 2024 15:02:22 +0100
Subject: [PATCH 079/154] use separate Algorithm for Notifications and
RiskLevel evaluation
---
.../detection/TrackingDetectorWorker.kt | 49 +++++++++++++------
.../util/risk/RiskLevelEvaluator.kt | 13 ++---
2 files changed, 40 insertions(+), 22 deletions(-)
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 1a978ba3..56af2c0a 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
@@ -16,11 +16,10 @@ import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.database.repository.NotificationRepository
import de.seemoo.at_tracking_detection.notifications.NotificationService
import de.seemoo.at_tracking_detection.util.SharedPrefs
-import de.seemoo.at_tracking_detection.util.risk.RiskLevel
import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
import timber.log.Timber
+import java.time.Duration
import java.time.LocalDateTime
-import java.time.temporal.ChronoUnit
import java.util.concurrent.ConcurrentHashMap
@HiltWorker
@@ -51,7 +50,7 @@ class TrackingDetectorWorker @AssistedInject constructor(
val device = deviceRepository.getDevice(mapEntry.key) ?: return@forEach
val useLocation = SharedPrefs.useLocationInTrackingDetection
- if (RiskLevelEvaluator.checkRiskLevelForDevice(device, useLocation) != RiskLevel.LOW && checkLastNotification(device)) {
+ if (throwNotification(device, useLocation)) {
// Send Notification
Timber.d("Conditions for device ${device.address} being a tracking device are true... Sending Notification!")
notificationService.sendTrackingNotification(device)
@@ -88,6 +87,37 @@ class TrackingDetectorWorker @AssistedInject constructor(
return beaconsPerDevice
}
+ private fun throwNotification(device: BaseDevice, useLocation: Boolean): Boolean {
+ val minNumberOfLocations: Int = RiskLevelEvaluator.getNumberOfLocationsToBeConsideredForTrackingDetection(device.deviceType) // TODO safe call because deviceType can be null
+ val minTrackedTime: Long = RiskLevelEvaluator.getMinutesAtLeastTrackedBeforeAlarm() // in minutes
+
+ val deviceIdentifier: String = device.address
+ val relevantHours: Long = device.deviceType?.getNumberOfHoursToBeConsideredForTrackingDetection() ?: RiskLevelEvaluator.RELEVANT_HOURS_TRACKING
+ var considerDetectionEventSince: LocalDateTime = RiskLevelEvaluator.getRelevantTrackingDate(relevantHours)
+
+ val lastNotificationSent = device.lastNotificationSent
+ if (lastNotificationSent != null && lastNotificationSent > considerDetectionEventSince && lastNotificationSent < LocalDateTime.now()) {
+ considerDetectionEventSince = lastNotificationSent
+ }
+
+ val detectionEvents: List = beaconRepository.getDeviceBeaconsSince(deviceIdentifier, considerDetectionEventSince)
+
+ val detectionEventsSorted: List = detectionEvents.sortedBy { it.receivedAt }
+ val earliestDetectionEvent: Beacon = detectionEventsSorted.firstOrNull() ?: return false
+ val timeFollowing: Long = Duration.between(earliestDetectionEvent.receivedAt, LocalDateTime.now()).toMinutes()
+
+ val filteredDetectionEvents = detectionEvents.filter { it.locationId != null && it.locationId != 0 }
+ val distinctDetectionEvent = filteredDetectionEvents.map { it.locationId }.distinct()
+ val locations = distinctDetectionEvent.size
+
+ if (timeFollowing >= minTrackedTime) {
+ if (locations >= minNumberOfLocations || !useLocation) {
+ return true
+ }
+ }
+ return false
+ }
+
companion object {
fun getLocation(latitude: Double, longitude: Double): Location {
val location = Location(LocationManager.GPS_PROVIDER)
@@ -95,19 +125,6 @@ class TrackingDetectorWorker @AssistedInject constructor(
location.longitude = longitude
return location
}
-
- /**
- * Checks if the last notification was sent more than x hours ago
- */
- private fun checkLastNotification(device: BaseDevice): Boolean {
- val lastNotificationSent = device.lastNotificationSent
- return lastNotificationSent == null || isTimeToNotify(lastNotificationSent)
- }
-
- private fun isTimeToNotify(lastNotificationSent: LocalDateTime): Boolean {
- val hoursPassed = lastNotificationSent.until(LocalDateTime.now(), ChronoUnit.HOURS)
- return hoursPassed >= RiskLevelEvaluator.HOURS_AT_LEAST_UNTIL_NEXT_NOTIFICATION
- }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
index 90418043..b4ea9ac8 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
@@ -89,11 +89,11 @@ class RiskLevelEvaluator(
/** The number of days that we use to calculate the risk **/
const val RELEVANT_HOURS_TRACKING: Long = 24 // Only consider beacons in the last x hours, default value, can be overwritten in the specific device properties
const val RELEVANT_DAYS_RISK_LEVEL: Long = 14
+ private const val MINUTES_UNTIL_CACHE_IS_UPDATED: Long = 15
private const val NUMBER_OF_NOTIFICATIONS_FOR_HIGH_RISK: Long = 2 // After x MEDIUM risk notifications (for a single device) change risk level to HIGH
private const val RELEVANT_DAYS_NOTIFICATIONS: Long = 5 // After MEDIUM risk notifications in the last x days (for a single device) change risk level to HIGH
private const val NUMBER_OF_BEACONS_BEFORE_ALARM: Int = 3 // Number of total beacons before notification is created
private const val MAX_ACCURACY_FOR_LOCATIONS: Float = 100.0F // Minimum Location accuracy for high risk
- const val HOURS_AT_LEAST_UNTIL_NEXT_NOTIFICATION: Long = 8 // Minimum time difference until next notification
const val MAX_NUMBER_MEDIUM_RISK: Long = 3 // Maximum number of devices with MEDIUM risk until the total risk level is set to high
val relevantTrackingDateDefault: LocalDateTime = LocalDateTime.now().minusHours(
RELEVANT_HOURS_TRACKING) // Fallback Option, if possible use getRelevantTrackingDate() Function
@@ -113,7 +113,7 @@ class RiskLevelEvaluator(
getMinutesAtLeastTrackedBeforeAlarm()
)
- private fun getRelevantTrackingDate(relevantDays: Long): LocalDateTime = LocalDateTime.now().minusDays(relevantDays)
+ fun getRelevantTrackingDate(relevantHours: Long): LocalDateTime = LocalDateTime.now().minusHours(relevantHours)
fun getMinutesAtLeastTrackedBeforeAlarm(): Long {
return when (SharedPrefs.riskSensitivity) {
@@ -124,7 +124,7 @@ class RiskLevelEvaluator(
}
}
- private fun getNumberOfLocationsToBeConsideredForTrackingDetection(deviceType: DeviceType?): Int {
+ fun getNumberOfLocationsToBeConsideredForTrackingDetection(deviceType: DeviceType?): Int {
return if (deviceType == null) {
when (SharedPrefs.riskSensitivity) {
"low" -> NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW
@@ -152,8 +152,8 @@ class RiskLevelEvaluator(
// Not ignored
// Tracker has been seen long enough
if (!device.ignore && device.firstDiscovery <= getAtLeastTrackedSince()) {
- val relevantDays: Long = device.deviceType?.getNumberOfHoursToBeConsideredForTrackingDetection() ?: RELEVANT_HOURS_TRACKING
- val relevantTrackingDate = getRelevantTrackingDate(relevantDays)
+ val relevantHours: Long = device.deviceType?.getNumberOfHoursToBeConsideredForTrackingDetection() ?: RELEVANT_HOURS_TRACKING
+ val relevantTrackingDate = getRelevantTrackingDate(relevantHours)
val numberOfBeacons = beaconRepository.getNumberOfBeaconsAddress(device.address, relevantTrackingDate)
// Detected at least 3 Times
@@ -164,7 +164,8 @@ class RiskLevelEvaluator(
val lastCalculatedRiskLevel = deviceRepository.getLastCachedRiskLevelDate(device.address)
if (lastCalculatedRiskLevel != null) {
- if (lastCalculatedRiskLevel >= LocalDateTime.now().minusMinutes(15)) {
+ if (lastCalculatedRiskLevel >= LocalDateTime.now().minusMinutes(
+ MINUTES_UNTIL_CACHE_IS_UPDATED)) {
fun cachedRiskIntToRisk(cachedRiskInt: Int): RiskLevel {
return when (cachedRiskInt) {
0 -> RiskLevel.LOW
From 44b556e73db3ce3f4825b9372a7bb78bf4fd5ad5 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 19 Feb 2024 17:25:48 +0100
Subject: [PATCH 080/154] optimize and Bugfix RiskLevelEvaluator
---
.../at_tracking_detection/ScanDBTests.kt | 2 +-
.../database/repository/DeviceRepository.kt | 6 +++---
.../database/repository/ScanRepository.kt | 17 +++++++----------
.../detection/TrackingDetectorWorker.kt | 6 +++---
.../ui/dashboard/DeviceMapViewModel.kt | 2 +-
.../ui/dashboard/RiskDetailViewModel.kt | 4 ++--
.../ui/devices/AllDevicesViewModel.kt | 2 +-
.../ui/devices/DevicesFragment.kt | 2 +-
.../util/risk/RiskLevelEvaluator.kt | 19 +++++++++----------
9 files changed, 28 insertions(+), 32 deletions(-)
diff --git a/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanDBTests.kt b/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanDBTests.kt
index 26286ef8..70e4a490 100644
--- a/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanDBTests.kt
+++ b/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanDBTests.kt
@@ -124,7 +124,7 @@ class ScanDBTests() {
assert(it.endDate == null)
assert(it.startDate == null)
assert(
- (it.startDate?.compareTo(RiskLevelEvaluator.relevantTrackingDateDefault) ?: -1) >= 0
+ (it.startDate?.compareTo(RiskLevelEvaluator.relevantTrackingDateForRiskCalculation) ?: -1) >= 0
)
}
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt
index 01efcfdd..82816e6e 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt
@@ -46,12 +46,12 @@ class DeviceRepository @Inject constructor(private val deviceDao: DeviceDao) {
fun getDevice(deviceAddress: String): BaseDevice? = deviceDao.getByAddress(deviceAddress)
- val countNotTracking = deviceDao.getCountNotTracking(RiskLevelEvaluator.relevantTrackingDateDefault)
+ val countNotTracking = deviceDao.getCountNotTracking(RiskLevelEvaluator.relevantTrackingDateForRiskCalculation)
val countIgnored = deviceDao.getCountIgnored()
- fun countForDeviceType(deviceType: DeviceType) = deviceDao.getCountForType(deviceType.name, RiskLevelEvaluator.relevantTrackingDateDefault)
- fun countForDeviceTypes(deviceType1: DeviceType, deviceType2: DeviceType) = deviceDao.getCountForTypes(deviceType1.name, deviceType2.name, RiskLevelEvaluator.relevantTrackingDateDefault)
+ fun countForDeviceType(deviceType: DeviceType) = deviceDao.getCountForType(deviceType.name, RiskLevelEvaluator.relevantTrackingDateForRiskCalculation)
+ fun countForDeviceTypes(deviceType1: DeviceType, deviceType2: DeviceType) = deviceDao.getCountForTypes(deviceType1.name, deviceType2.name, RiskLevelEvaluator.relevantTrackingDateForRiskCalculation)
fun getNumberOfLocationsForDeviceSince(deviceAddress: String, since: LocalDateTime): Int = deviceDao.getNumberOfLocationsForDevice(deviceAddress, since)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/ScanRepository.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/ScanRepository.kt
index c33f43db..972048fc 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/ScanRepository.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/ScanRepository.kt
@@ -14,16 +14,13 @@ class ScanRepository @Inject constructor(
var lastScan = scanDao.lastScan()
var relevantScans =
- scanDao.getScansSince(RiskLevelEvaluator.relevantTrackingDateDefault)
+ scanDao.getScansSince(RiskLevelEvaluator.getRelevantTrackingDateForTrackingDetection())
- fun relevantScans(manual: Boolean, limit: Int): List = scanDao.getScansSince(RiskLevelEvaluator.relevantTrackingDateDefault, manual, limit)
+ fun relevantScans(manual: Boolean, limit: Int): List = scanDao.getScansSince(RiskLevelEvaluator.getRelevantTrackingDateForTrackingDetection(), manual, limit)
- val relevantDebugScans = scanDao.getDebugScansSince(RiskLevelEvaluator.relevantTrackingDateDefault)
+ val relevantDebugScans = scanDao.getDebugScansSince(RiskLevelEvaluator.getRelevantTrackingDateForTrackingDetection())
- var flowRelevantScans =
- scanDao.getFlowScansSince(RiskLevelEvaluator.relevantTrackingDateDefault)
-
- val flowDebugScans = scanDao.getFlowDebugRelevantScans(RiskLevelEvaluator.relevantTrackingDateDefault)
+ val flowDebugScans = scanDao.getFlowDebugRelevantScans(RiskLevelEvaluator.getRelevantTrackingDateForTrackingDetection())
var allScans: List = scanDao.getAllScans()
@@ -31,15 +28,15 @@ class ScanRepository @Inject constructor(
val totalCount: Int = scanDao.getNumberOfScans()
- var countInRelevantTime: Int = scanDao.getNumberOfScansSince(RiskLevelEvaluator.relevantTrackingDateDefault)
+ var countInRelevantTime: Int = scanDao.getNumberOfScansSince(RiskLevelEvaluator.getRelevantTrackingDateForTrackingDetection())
- val relevantUnfinishedScans: List = scanDao.unfinishedScans(RiskLevelEvaluator.relevantTrackingDateDefault)
+ val relevantUnfinishedScans: List = scanDao.unfinishedScans(RiskLevelEvaluator.getRelevantTrackingDateForTrackingDetection())
@WorkerThread
suspend fun insert(scan: Scan): Long = scanDao.insert(scan)
@WorkerThread
- suspend fun deleteIrrelevantScans() = scanDao.deleteUntil(RiskLevelEvaluator.relevantTrackingDateDefault)
+ suspend fun deleteIrrelevantScans() = scanDao.deleteUntil(RiskLevelEvaluator.relevantTrackingDateForRiskCalculation)
@WorkerThread
suspend fun update(scan: Scan) = scanDao.update(scan)
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 56af2c0a..47109041 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
@@ -81,19 +81,19 @@ class TrackingDetectorWorker @AssistedInject constructor(
//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 {
// Only retrieve the last two weeks since they are only relevant for tracking
- val beacons = beaconRepository.getDeviceBeaconsSince(it.deviceAddress, RiskLevelEvaluator.relevantTrackingDateDefault)
+ val beacons = beaconRepository.getDeviceBeaconsSince(it.deviceAddress, RiskLevelEvaluator.relevantTrackingDateForRiskCalculation)
beaconsPerDevice[it.deviceAddress] = beacons
}
return beaconsPerDevice
}
private fun throwNotification(device: BaseDevice, useLocation: Boolean): Boolean {
- val minNumberOfLocations: Int = RiskLevelEvaluator.getNumberOfLocationsToBeConsideredForTrackingDetection(device.deviceType) // TODO safe call because deviceType can be null
+ val minNumberOfLocations: Int = RiskLevelEvaluator.getNumberOfLocationsToBeConsideredForTrackingDetection(device.deviceType)
val minTrackedTime: Long = RiskLevelEvaluator.getMinutesAtLeastTrackedBeforeAlarm() // in minutes
val deviceIdentifier: String = device.address
val relevantHours: Long = device.deviceType?.getNumberOfHoursToBeConsideredForTrackingDetection() ?: RiskLevelEvaluator.RELEVANT_HOURS_TRACKING
- var considerDetectionEventSince: LocalDateTime = RiskLevelEvaluator.getRelevantTrackingDate(relevantHours)
+ var considerDetectionEventSince: LocalDateTime = RiskLevelEvaluator.getRelevantTrackingDateForTrackingDetection(relevantHours)
val lastNotificationSent = device.lastNotificationSent
if (lastNotificationSent != null && lastNotificationSent > considerDetectionEventSince && lastNotificationSent < LocalDateTime.now()) {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapViewModel.kt
index e2e511cf..6f43c99e 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapViewModel.kt
@@ -17,7 +17,7 @@ class DeviceMapViewModel @Inject constructor(
beaconRepository.getDeviceBeacons(it)
}
- val allLocations: LiveData> = beaconRepository.getBeaconsSince(RiskLevelEvaluator.relevantTrackingDateDefault).asLiveData()
+ val allLocations: LiveData> = beaconRepository.getBeaconsSince(RiskLevelEvaluator.relevantTrackingDateForRiskCalculation).asLiveData()
val isMapLoading = MutableLiveData(false)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailViewModel.kt
index d64b8a5a..091e2e47 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailViewModel.kt
@@ -31,14 +31,14 @@ class RiskDetailViewModel @Inject constructor(
val locationRepository: LocationRepository,
) : ViewModel() {
- private val relevantDate = RiskLevelEvaluator.relevantTrackingDateDefault
+ private val relevantDate = RiskLevelEvaluator.relevantTrackingDateForRiskCalculation
private val trackersFound: List = deviceRepository.trackingDevicesNotIgnoredSince(relevantDate)
private val lastSeenDates = trackersFound.map {
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).format(it.lastSeen)
}
var riskColor: Int
- val numberOfTrackersFound = deviceRepository.trackingDevicesNotIgnoredSinceCount(RiskLevelEvaluator.relevantTrackingDateDefault).asLiveData()
+ val numberOfTrackersFound = deviceRepository.trackingDevicesNotIgnoredSinceCount(RiskLevelEvaluator.relevantTrackingDateForRiskCalculation).asLiveData()
val totalLocationsTrackedCount= locationRepository.locationsSinceCount(relevantDate).asLiveData()
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 2d6c4b86..09efdb49 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
@@ -20,7 +20,7 @@ class AllDevicesViewModel @Inject constructor(
val countNotTracking = deviceRepository.countNotTracking.asLiveData()
val countIgnored = deviceRepository.countIgnored.asLiveData()
- val countTracking = deviceRepository.trackingDevicesNotIgnoredSinceCount(RiskLevelEvaluator.relevantTrackingDateDefault).asLiveData()
+ val countTracking = deviceRepository.trackingDevicesNotIgnoredSinceCount(RiskLevelEvaluator.relevantTrackingDateForRiskCalculation).asLiveData()
val countAirTags = deviceRepository.countForDeviceType(DeviceType.AIRTAG).asLiveData()
val countFindMy = deviceRepository.countForDeviceType(DeviceType.FIND_MY).asLiveData()
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DevicesFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DevicesFragment.kt
index 70785a70..e72ba72c 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DevicesFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DevicesFragment.kt
@@ -75,7 +75,7 @@ abstract class DevicesFragment(
)
)
} else {
- val relevantTrackingStartDate = RiskLevelEvaluator.relevantTrackingDateDefault.toLocalDate()
+ val relevantTrackingStartDate = RiskLevelEvaluator.relevantTrackingDateForRiskCalculation.toLocalDate()
devicesViewModel.addOrRemoveFilter(
DateRangeFilter.build(
relevantTrackingStartDate,
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
index b4ea9ac8..5d5ab1e2 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
@@ -26,7 +26,7 @@ class RiskLevelEvaluator(
fun evaluateRiskLevel(): RiskLevel {
Timber.d("evaluateRiskLevel() called")
// val baseDevices: List = deviceRepository.trackingDevicesSince(relevantTrackingDate)
- val baseDevices: List = deviceRepository.trackingDevicesNotIgnoredSince(relevantTrackingDateDefault)
+ val baseDevices: List = deviceRepository.trackingDevicesNotIgnoredSince(relevantTrackingDateForRiskCalculation)
val totalTrackers = baseDevices.count()
@@ -68,7 +68,7 @@ class RiskLevelEvaluator(
* The date when a tracker has been discovered last
*/
fun getLastTrackerDiscoveryDate(): Date {
- val relevantDate = relevantTrackingDateDefault
+ val relevantDate = relevantTrackingDateForRiskCalculation
val baseDevices: List = deviceRepository.trackingDevicesSince(relevantDate)
.sortedByDescending { it.lastSeen }
@@ -78,8 +78,8 @@ class RiskLevelEvaluator(
}
// How many trackers have been relevant here as tracker
- fun getNumberRelevantTrackers(): Int {
- val relevantDate = LocalDateTime.now().minusHours(RELEVANT_HOURS_TRACKING)
+ fun getNumberRelevantTrackers(relevantDays: Long = RELEVANT_DAYS_RISK_LEVEL): Int {
+ val relevantDate = LocalDateTime.now().minusDays(relevantDays)
val baseDevices: List = deviceRepository.trackingDevicesNotIgnoredSince(relevantDate)
return baseDevices.count()
@@ -88,15 +88,15 @@ class RiskLevelEvaluator(
companion object {
/** The number of days that we use to calculate the risk **/
const val RELEVANT_HOURS_TRACKING: Long = 24 // Only consider beacons in the last x hours, default value, can be overwritten in the specific device properties
- const val RELEVANT_DAYS_RISK_LEVEL: Long = 14
+ private const val RELEVANT_DAYS_RISK_LEVEL: Long = 14
private const val MINUTES_UNTIL_CACHE_IS_UPDATED: Long = 15
private const val NUMBER_OF_NOTIFICATIONS_FOR_HIGH_RISK: Long = 2 // After x MEDIUM risk notifications (for a single device) change risk level to HIGH
private const val RELEVANT_DAYS_NOTIFICATIONS: Long = 5 // After MEDIUM risk notifications in the last x days (for a single device) change risk level to HIGH
private const val NUMBER_OF_BEACONS_BEFORE_ALARM: Int = 3 // Number of total beacons before notification is created
private const val MAX_ACCURACY_FOR_LOCATIONS: Float = 100.0F // Minimum Location accuracy for high risk
const val MAX_NUMBER_MEDIUM_RISK: Long = 3 // Maximum number of devices with MEDIUM risk until the total risk level is set to high
- val relevantTrackingDateDefault: LocalDateTime = LocalDateTime.now().minusHours(
- RELEVANT_HOURS_TRACKING) // Fallback Option, if possible use getRelevantTrackingDate() Function
+ val relevantTrackingDateForRiskCalculation: LocalDateTime = LocalDateTime.now().minusDays(
+ RELEVANT_DAYS_RISK_LEVEL) // Fallback Option, if possible use getRelevantTrackingDate() Function
private val relevantNotificationDate: LocalDateTime = LocalDateTime.now().minusDays(RELEVANT_DAYS_NOTIFICATIONS)
// Default Values: A single tracker gets tracked at least for x minutes until notification is created
@@ -113,7 +113,7 @@ class RiskLevelEvaluator(
getMinutesAtLeastTrackedBeforeAlarm()
)
- fun getRelevantTrackingDate(relevantHours: Long): LocalDateTime = LocalDateTime.now().minusHours(relevantHours)
+ fun getRelevantTrackingDateForTrackingDetection(relevantHours: Long = RELEVANT_HOURS_TRACKING): LocalDateTime = LocalDateTime.now().minusHours(relevantHours)
fun getMinutesAtLeastTrackedBeforeAlarm(): Long {
return when (SharedPrefs.riskSensitivity) {
@@ -152,8 +152,7 @@ class RiskLevelEvaluator(
// Not ignored
// Tracker has been seen long enough
if (!device.ignore && device.firstDiscovery <= getAtLeastTrackedSince()) {
- val relevantHours: Long = device.deviceType?.getNumberOfHoursToBeConsideredForTrackingDetection() ?: RELEVANT_HOURS_TRACKING
- val relevantTrackingDate = getRelevantTrackingDate(relevantHours)
+ val relevantTrackingDate = relevantTrackingDateForRiskCalculation
val numberOfBeacons = beaconRepository.getNumberOfBeaconsAddress(device.address, relevantTrackingDate)
// Detected at least 3 Times
From 6d05b83af40ea757073701148ef3ce5bc7c0ad47 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 22 Feb 2024 14:24:46 +0100
Subject: [PATCH 081/154] add feedback options other and not found. Add message
success when submitting feedback.
---
.../ui/feedback/FeedbackFragment.kt | 11 ++++++++++-
app/src/main/res/values-de/strings.xml | 3 +++
app/src/main/res/values/strings.xml | 3 +++
3 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/FeedbackFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/FeedbackFragment.kt
index 01e78ee1..2ef78067 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/FeedbackFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/FeedbackFragment.kt
@@ -1,9 +1,12 @@
package de.seemoo.at_tracking_detection.ui.feedback
import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -39,7 +42,8 @@ class FeedbackFragment : Fragment() {
val locations = arrayOf(
R.string.feedback_location_backpack, R.string.feedback_location_clothes,
- R.string.feedback_location_car, R.string.feedback_location_bike
+ R.string.feedback_location_car, R.string.feedback_location_bike,
+ R.string.feedback_location_other, R.string.feedback_location_not_found
)
for (location in locations) {
val chip =
@@ -56,6 +60,11 @@ class FeedbackFragment : Fragment() {
}
chip.setOnClickListener {
feedbackViewModel.location.postValue(getString(location))
+ Toast.makeText(requireContext(), R.string.feedback_success, Toast.LENGTH_SHORT).show()
+ Handler(Looper.getMainLooper()).postDelayed({
+ requireActivity().supportFragmentManager.beginTransaction().remove(this).commit()
+ // Show a Toast message indicating success
+ }, 200)
}
locationChipGroup.addView(chip)
}
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 3b67b144..d5b266a3 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -50,6 +50,9 @@
Kleidung
Auto
Fahrrad
+ Woanders
+ Tracker nicht gefunden
+ Feedback wurde übermittel
Schließen
Ton wird abgespielt…
Berechtigung erforderlich
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4cc424f5..67c542f0 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -55,6 +55,9 @@
Clothes
Car
Bike
+ Other
+ Tracker was not found
+ Feedback submitted successfully
Close
Playing sound…
Permission required
From b17376d507ad64e8345413600c9171a7c2ae13e0 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 22 Feb 2024 15:37:19 +0100
Subject: [PATCH 082/154] save sensitivity level for every notification, save
connectionState for every new beacon
---
.../13.json | 411 ++++++++++++++++++
.../ExampleInstrumentedTest.kt | 36 +-
.../database/AppDatabase.kt | 3 +-
.../database/models/Beacon.kt | 12 +-
.../database/models/Notification.kt | 8 +-
.../viewmodel/NotificationViewModel.kt | 4 +-
.../detection/ScanBluetoothWorker.kt | 10 +-
.../statistics/SendStatisticsWorker.kt | 3 +-
.../at_tracking_detection/util/Utility.kt | 20 +
9 files changed, 482 insertions(+), 25 deletions(-)
create mode 100644 app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/13.json
diff --git a/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/13.json b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/13.json
new file mode 100644
index 00000000..0fb164e7
--- /dev/null
+++ b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/13.json
@@ -0,0 +1,411 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 13,
+ "identityHash": "d829378e4cbf1035e49924090f632174",
+ "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, `riskLevel` INTEGER NOT NULL DEFAULT 0, `lastCalculatedRiskDate` TEXT, `nextObservationNotification` TEXT, `currentObservationDuration` INTEGER)",
+ "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
+ },
+ {
+ "fieldPath": "riskLevel",
+ "columnName": "riskLevel",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "lastCalculatedRiskDate",
+ "columnName": "lastCalculatedRiskDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextObservationNotification",
+ "columnName": "nextObservationNotification",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "currentObservationDuration",
+ "columnName": "currentObservationDuration",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "deviceId"
+ ]
+ },
+ "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, `sensitivity` INTEGER NOT NULL DEFAULT 0)",
+ "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
+ },
+ {
+ "fieldPath": "sensitivity",
+ "columnName": "sensitivity",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "notificationId"
+ ]
+ },
+ "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, `locationId` INTEGER, `mfg` BLOB, `serviceUUIDs` TEXT, `connectionState` TEXT NOT NULL DEFAULT 'UNKNOWN')",
+ "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": "locationId",
+ "columnName": "locationId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "manufacturerData",
+ "columnName": "mfg",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serviceUUIDs",
+ "columnName": "serviceUUIDs",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "connectionState",
+ "columnName": "connectionState",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'UNKNOWN'"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "beaconId"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "feedbackId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "scan",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scanId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `endDate` TEXT, `noDevicesFound` INTEGER, `duration` INTEGER, `isManual` INTEGER NOT NULL, `scanMode` INTEGER NOT NULL, `startDate` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "scanId",
+ "columnName": "scanId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endDate",
+ "columnName": "endDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "noDevicesFound",
+ "columnName": "noDevicesFound",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "duration",
+ "columnName": "duration",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isManual",
+ "columnName": "isManual",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "scanMode",
+ "columnName": "scanMode",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startDate",
+ "columnName": "startDate",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "scanId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "location",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`locationId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `firstDiscovery` TEXT NOT NULL, `lastSeen` TEXT NOT NULL, `longitude` REAL NOT NULL, `latitude` REAL NOT NULL, `accuracy` REAL)",
+ "fields": [
+ {
+ "fieldPath": "locationId",
+ "columnName": "locationId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstDiscovery",
+ "columnName": "firstDiscovery",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastSeen",
+ "columnName": "lastSeen",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longitude",
+ "columnName": "longitude",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latitude",
+ "columnName": "latitude",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accuracy",
+ "columnName": "accuracy",
+ "affinity": "REAL",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "locationId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_location_latitude_longitude",
+ "unique": true,
+ "columnNames": [
+ "latitude",
+ "longitude"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_location_latitude_longitude` ON `${TABLE_NAME}` (`latitude`, `longitude`)"
+ }
+ ],
+ "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, 'd829378e4cbf1035e49924090f632174')"
+ ]
+ }
+}
\ 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 d4d57ed9..ae1c6350 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
@@ -61,7 +61,8 @@ class ExampleInstrumentedTest {
// latitude = 51.4839483,
locationId = 1,
mfg = null,
- serviceUUIDs = null
+ serviceUUIDs = null,
+ "UNKNOWN"
),
Beacon(
receivedAt = LocalDateTime.of(2021, 11, 20, 10, 30),
@@ -71,7 +72,8 @@ class ExampleInstrumentedTest {
// latitude = 51.4839483,
locationId = 1,
mfg = null,
- serviceUUIDs = null
+ serviceUUIDs = null,
+ "UNKNOWN"
)
)
@@ -84,7 +86,8 @@ class ExampleInstrumentedTest {
// latitude = 51.4839483,
locationId = 1,
mfg = null,
- serviceUUIDs = null
+ serviceUUIDs = null,
+ "UNKNOWN"
),
Beacon(
receivedAt = LocalDateTime.of(2021, 11, 20, 10, 45),
@@ -94,7 +97,8 @@ class ExampleInstrumentedTest {
// latitude = 51.4839483,
locationId = 1,
mfg = null,
- serviceUUIDs = null
+ serviceUUIDs = null,
+ "UNKNOWN"
)
)
@@ -107,7 +111,8 @@ class ExampleInstrumentedTest {
// latitude = 51.4839483,
locationId = 1,
mfg = null,
- serviceUUIDs = null
+ serviceUUIDs = null,
+ "UNKNOWN"
),
Beacon(
receivedAt = LocalDateTime.of(2021, 11, 22, 10, 45),
@@ -117,7 +122,8 @@ class ExampleInstrumentedTest {
// latitude = 51.4839483,
locationId = 1,
mfg = null,
- serviceUUIDs = null
+ serviceUUIDs = null,
+ "UNKNOWN"
)
)
@@ -130,7 +136,8 @@ class ExampleInstrumentedTest {
// latitude = 51.4839483,
locationId = null,
mfg = null,
- serviceUUIDs = null
+ serviceUUIDs = null,
+ "UNKNOWN"
),
Beacon(
receivedAt = LocalDateTime.of(2021, 11, 20, 10, 0),
@@ -140,7 +147,8 @@ class ExampleInstrumentedTest {
// latitude = 51.4839483,
locationId = null,
mfg = null,
- serviceUUIDs = null
+ serviceUUIDs = null,
+ "UNKNOWN"
)
)
@@ -153,7 +161,8 @@ class ExampleInstrumentedTest {
// latitude = 51.4839483,
locationId = null,
mfg = null,
- serviceUUIDs = null
+ serviceUUIDs = null,
+ "UNKNOWN"
),
Beacon(
receivedAt = LocalDateTime.of(2021, 11, 20, 10, 29),
@@ -163,7 +172,8 @@ class ExampleInstrumentedTest {
// latitude = 51.4839483,
locationId = null,
mfg = null,
- serviceUUIDs = null
+ serviceUUIDs = null,
+ "UNKNOWN"
)
)
@@ -176,7 +186,8 @@ class ExampleInstrumentedTest {
// latitude = 51.4839483,
locationId = null,
mfg = null,
- serviceUUIDs = null
+ serviceUUIDs = null,
+ "UNKNOWN"
),
Beacon(
receivedAt = LocalDateTime.of(2021, 11, 20, 10, 20),
@@ -186,7 +197,8 @@ class ExampleInstrumentedTest {
// latitude = 51.4839483,
locationId = null,
mfg = null,
- serviceUUIDs = null
+ serviceUUIDs = null,
+ "UNKNOWN"
)
)
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 53471d6e..314664f7 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
@@ -8,7 +8,7 @@ import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.util.converter.DateTimeConverter
@Database(
- version = 12,
+ version = 13,
entities = [
BaseDevice::class,
Notification::class,
@@ -26,6 +26,7 @@ import de.seemoo.at_tracking_detection.util.converter.DateTimeConverter
AutoMigration(from=8, to=9, spec = AppDatabase.RenameScanMigrationSpec::class),
AutoMigration(from=10, to=11),
AutoMigration(from=11, to=12),
+ AutoMigration(from=12, to=13),
],
exportSchema = true
)
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 9c2b749a..2959bc6f 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
@@ -19,7 +19,8 @@ data class Beacon(
@ColumnInfo(name = "deviceAddress") var deviceAddress: String,
@ColumnInfo(name = "locationId") var locationId: Int?,
@ColumnInfo(name = "mfg") var manufacturerData: ByteArray?,
- @ColumnInfo(name = "serviceUUIDs") var serviceUUIDs: List?
+ @ColumnInfo(name = "serviceUUIDs") var serviceUUIDs: List?,
+ @ColumnInfo(name = "connectionState", defaultValue = "UNKNOWN") var connectionState: String
) {
constructor(
receivedAt: LocalDateTime,
@@ -27,7 +28,8 @@ data class Beacon(
deviceAddress: String,
locationId: Int?,
mfg: ByteArray?,
- serviceUUIDs: List?
+ serviceUUIDs: List?,
+ connectionState: String
) : this(
0,
receivedAt,
@@ -35,7 +37,8 @@ data class Beacon(
deviceAddress,
locationId,
mfg,
- serviceUUIDs
+ serviceUUIDs,
+ connectionState
)
fun getFormattedDate(): String =
@@ -53,6 +56,7 @@ data class Beacon(
if (rssi != other.rssi) return false
if (deviceAddress != other.deviceAddress) return false
if (locationId != other.locationId) return false
+ if (connectionState != other.connectionState) return false
return true
}
@@ -60,7 +64,7 @@ data class Beacon(
override fun hashCode(): Int {
var result = beaconId
result = 31 * result + receivedAt.hashCode()
- result = 31 * result + rssi
+ result = 31 * result + rssi.hashCode()
result = 31 * result + deviceAddress.hashCode()
result = 31 * result + locationId.hashCode()
return result
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Notification.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Notification.kt
index 520e9b1d..837eb9e7 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Notification.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Notification.kt
@@ -15,11 +15,13 @@ data class Notification(
@ColumnInfo(name = "falseAlarm") val falseAlarm: Boolean,
@ColumnInfo(name = "dismissed") val dismissed: Boolean?,
@ColumnInfo(name = "clicked") val clicked: Boolean?,
- @ColumnInfo(name = "createdAt") val createdAt: LocalDateTime
+ @ColumnInfo(name = "createdAt") val createdAt: LocalDateTime,
+ @ColumnInfo(name = "sensitivity", defaultValue = "0") val sensitivity: Int // 0 = unknown, 1 = low, 2 = medium, 3 = high
) {
constructor(
deviceAddress: String,
falseAlarm: Boolean,
- createdAt: LocalDateTime
- ) : this(0, deviceAddress, falseAlarm, false, false, createdAt)
+ createdAt: LocalDateTime,
+ sensitivity: Int
+ ) : this(0, deviceAddress, falseAlarm, false, false, createdAt, sensitivity)
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/viewmodel/NotificationViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/viewmodel/NotificationViewModel.kt
index 78c48af8..1b134b44 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/viewmodel/NotificationViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/viewmodel/NotificationViewModel.kt
@@ -2,6 +2,7 @@ package de.seemoo.at_tracking_detection.database.viewmodel
import de.seemoo.at_tracking_detection.database.models.Notification
import de.seemoo.at_tracking_detection.database.repository.NotificationRepository
+import de.seemoo.at_tracking_detection.util.Utility
import java.time.LocalDateTime
import java.time.ZoneOffset
import javax.inject.Inject
@@ -9,7 +10,8 @@ import javax.inject.Inject
class NotificationViewModel @Inject constructor(private val notificationRepository: NotificationRepository) {
suspend fun insert(deviceAddress: String): Int {
- val notification = Notification(deviceAddress, false, LocalDateTime.now(ZoneOffset.UTC))
+ val sensitivity = Utility.getSensitivity()
+ val notification = Notification(deviceAddress, false, LocalDateTime.now(ZoneOffset.UTC), sensitivity)
return notificationRepository.insert(notification).toInt()
}
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 873d6671..1c819d5b 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
@@ -262,6 +262,9 @@ class ScanBluetoothWorker @AssistedInject constructor(
val uuids = scanResult.scanRecord?.serviceUuids?.map { it.toString() }?.toList()
val uniqueIdentifier = getPublicKey(scanResult)
+ val connectionState: ConnectionState = BaseDevice.getConnectionState(scanResult)
+ val connectionStateString = Utility.connectionStateToString(connectionState)
+
var beacon: Beacon? = null
val beacons = beaconRepository.getDeviceBeaconsSince(
deviceAddress = uniqueIdentifier,
@@ -274,12 +277,12 @@ class ScanBluetoothWorker @AssistedInject constructor(
// Save the manufacturer data to the beacon
Beacon(
discoveryDate, scanResult.rssi, getPublicKey(scanResult), locId,
- scanResult.scanRecord?.bytes, uuids
+ scanResult.scanRecord?.bytes, uuids, connectionStateString
)
} else {
Beacon(
discoveryDate, scanResult.rssi, getPublicKey(scanResult), locId,
- null, uuids
+ null, uuids, connectionStateString
)
}
beaconRepository.insert(beacon)
@@ -288,6 +291,9 @@ class ScanBluetoothWorker @AssistedInject constructor(
Timber.d("Beacon already in the database... Adding Location")
beacon = beacons[0]
beacon.locationId = locId
+ if (beacon.connectionState == "UNKNOWN" && connectionState != ConnectionState.UNKNOWN) {
+ beacon.connectionState = connectionStateString
+ }
beaconRepository.update(beacon)
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/statistics/SendStatisticsWorker.kt b/app/src/main/java/de/seemoo/at_tracking_detection/statistics/SendStatisticsWorker.kt
index c7548fd3..f27e10b4 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/statistics/SendStatisticsWorker.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/statistics/SendStatisticsWorker.kt
@@ -83,8 +83,7 @@ class SendStatisticsWorker @AssistedInject constructor(
beacon.receivedAt >= uploadDateTime
}
it.beacons.forEach { beacon ->
- // beacon.latitude = null
- // beacon.longitude = null
+ // Remove location and device address
beacon.locationId = null
beacon.deviceAddress = ""
}
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 17313a97..84c767b1 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
@@ -14,6 +14,7 @@ import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale
import androidx.core.content.ContextCompat
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
+import de.seemoo.at_tracking_detection.database.models.device.ConnectionState
import de.seemoo.at_tracking_detection.database.models.Location as LocationModel
import de.seemoo.at_tracking_detection.ui.OnboardingActivity
import de.seemoo.at_tracking_detection.util.ble.DbmToPercent
@@ -194,6 +195,25 @@ object Utility {
return DbmToPercent.convert(rssi.toDouble(), perfectRssi = perfectRssi, worstRssi = worstRssi).toDouble() / 100.0
}
+ fun getSensitivity(): Int {
+ return when (SharedPrefs.riskSensitivity) {
+ "low" -> 1
+ "medium" -> 2
+ "high" -> 3
+ else -> 0
+ }
+ }
+
+ fun connectionStateToString(connectionState: ConnectionState): String {
+ return when (connectionState) {
+ ConnectionState.CONNECTED -> "CONNECTED"
+ ConnectionState.OFFLINE -> "OFFLINE"
+ ConnectionState.OVERMATURE_OFFLINE -> "OVERMATURE_OFFLINE"
+ ConnectionState.PREMATURE_OFFLINE -> "PREMATURE_OFFLINE"
+ ConnectionState.UNKNOWN -> "UNKNOWN"
+ }
+ }
+
private fun rssiToQuality(percentage: Float): Int {
return when (percentage) {
in 0.75..1.0 -> {
From f911ed8ea8ec5e603ac44a8ef26ffb158ea40595 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Fri, 23 Feb 2024 12:37:45 +0100
Subject: [PATCH 083/154] devices that have not been seen in the last 30 days
and beacons older than 30 days, both without a notification will get deleted
---
.../database/daos/BeaconDao.kt | 10 ++++++++--
.../database/daos/DeviceDao.kt | 8 +++++++-
.../database/repository/BeaconRepository.kt | 8 +++++++-
.../database/repository/DeviceRepository.kt | 12 ++++++++++++
.../detection/TrackingDetectorWorker.kt | 13 +++++++++++++
.../util/risk/RiskLevelEvaluator.kt | 2 ++
6 files changed, 49 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/BeaconDao.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/BeaconDao.kt
index b58ab5db..bc6baf96 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/BeaconDao.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/BeaconDao.kt
@@ -11,8 +11,8 @@ interface BeaconDao {
fun getAllBeacons(): List
// @Query("SELECT mfg FROM beacon WHERE mfg LIKE :Key LIMIT 1")
- @Query("SELECT * FROM beacon WHERE mfg LIKE :ServiceData")
- fun getBeaconsWithDataLike(ServiceData: String): List
+ @Query("SELECT * FROM beacon WHERE mfg LIKE :serviceData")
+ fun getBeaconsWithDataLike(serviceData: String): List
@Query("SELECT * FROM beacon WHERE receivedAt >= :since")
fun getLatestBeacons(since: LocalDateTime): List
@@ -69,4 +69,10 @@ interface BeaconDao {
@Delete
suspend fun delete(beacon: Beacon)
+
+ @Delete
+ suspend fun deleteBeacons(beacons: List)
+
+ @Query("SELECT * FROM beacon LEFT JOIN notification ON beacon.deviceAddress = notification.deviceAddress WHERE receivedAt < :deleteEverythingBefore AND notification.deviceAddress IS NULL")
+ fun getBeaconsOlderThanWithoutNotifications(deleteEverythingBefore: LocalDateTime): List
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/DeviceDao.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/DeviceDao.kt
index b6472947..8c21a683 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/DeviceDao.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/DeviceDao.kt
@@ -100,5 +100,11 @@ interface DeviceDao {
suspend fun update(baseDevice: BaseDevice)
@Delete
- suspend fun delete(baseDevice: BaseDevice)
+ suspend fun deleteDevice(baseDevice: BaseDevice)
+
+ @Delete
+ suspend fun deleteDevices(baseDevices: List)
+
+ @Query("SELECT * FROM device WHERE lastSeen < :since AND notificationSent == 0")
+ fun getDevicesOlderThanWithoutNotifications(since: LocalDateTime): List
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/BeaconRepository.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/BeaconRepository.kt
index 7db923a6..ebc6efeb 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/BeaconRepository.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/BeaconRepository.kt
@@ -1,6 +1,5 @@
package de.seemoo.at_tracking_detection.database.repository
-import android.os.ParcelUuid
import androidx.annotation.WorkerThread
import de.seemoo.at_tracking_detection.database.daos.BeaconDao
import de.seemoo.at_tracking_detection.database.models.Beacon
@@ -58,4 +57,11 @@ class BeaconRepository @Inject constructor(
suspend fun update(beacon: Beacon) {
beaconDao.update(beacon)
}
+
+ @WorkerThread
+ suspend fun deleteBeacons(beacons: List) {
+ beaconDao.deleteBeacons(beacons)
+ }
+
+ fun getBeaconsOlderThanWithoutNotifications(deleteEverythingBefore: LocalDateTime): List = beaconDao.getBeaconsOlderThanWithoutNotifications(deleteEverythingBefore)
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt
index 82816e6e..ba00bb00 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt
@@ -57,6 +57,8 @@ class DeviceRepository @Inject constructor(private val deviceDao: DeviceDao) {
fun getNumberOfLocationsForDeviceWithAccuracyLimitSince(deviceAddress: String, maxAccuracy: Float, since: LocalDateTime): Int = deviceDao.getNumberOfLocationsForWithAccuracyLimitDevice(deviceAddress, maxAccuracy, since)
+ fun getDevicesOlderThanWithoutNotifications(since: LocalDateTime): List = deviceDao.getDevicesOlderThanWithoutNotifications(since)
+
@WorkerThread
suspend fun getDeviceBeaconsSince(dateTime: String?): List {
return if (dateTime != null) {
@@ -88,4 +90,14 @@ class DeviceRepository @Inject constructor(private val deviceDao: DeviceDao) {
suspend fun setIgnoreFlag(deviceAddress: String, state: Boolean) {
deviceDao.setIgnoreFlag(deviceAddress, state)
}
+
+ @WorkerThread
+ suspend fun deleteDevice(baseDevice: BaseDevice) {
+ deviceDao.deleteDevice(baseDevice)
+ }
+
+ @WorkerThread
+ suspend fun deleteDevices(baseDevices: List) {
+ deviceDao.deleteDevices(baseDevices)
+ }
}
\ No newline at end of file
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 47109041..0d63cc20 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
@@ -33,6 +33,8 @@ class TrackingDetectorWorker @AssistedInject constructor(
) : CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
+ deleteOldAndSafeTrackers()
+
Timber.d("Tracking detection background job started!")
// Just writing a new comment in here.
val ignoredDevices = deviceRepository.ignoredDevicesSync
@@ -118,6 +120,17 @@ class TrackingDetectorWorker @AssistedInject constructor(
return false
}
+ private suspend fun deleteOldAndSafeTrackers() {
+ // Delete old devices and beacons from the database
+ Timber.d("Deleting old and safe Trackers")
+ val deleteSafeTrackersBefore = RiskLevelEvaluator.deleteBeforeDate
+ val beaconsToBeDeleted = beaconRepository.getBeaconsOlderThanWithoutNotifications(deleteSafeTrackersBefore)
+ beaconRepository.deleteBeacons(beaconsToBeDeleted)
+ val devicesToBeDeleted = deviceRepository.getDevicesOlderThanWithoutNotifications(deleteSafeTrackersBefore)
+ deviceRepository.deleteDevices(devicesToBeDeleted)
+ Timber.d("Deleting successful")
+ }
+
companion object {
fun getLocation(latitude: Double, longitude: Double): Location {
val location = Location(LocationManager.GPS_PROVIDER)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
index 5d5ab1e2..980dfead 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
@@ -88,6 +88,7 @@ class RiskLevelEvaluator(
companion object {
/** The number of days that we use to calculate the risk **/
const val RELEVANT_HOURS_TRACKING: Long = 24 // Only consider beacons in the last x hours, default value, can be overwritten in the specific device properties
+ private const val DELETE_SAFE_DEVICES_OLDER_THAN_DAYS: Long = 30 // Delete devices that have been seen last more than x days ago
private const val RELEVANT_DAYS_RISK_LEVEL: Long = 14
private const val MINUTES_UNTIL_CACHE_IS_UPDATED: Long = 15
private const val NUMBER_OF_NOTIFICATIONS_FOR_HIGH_RISK: Long = 2 // After x MEDIUM risk notifications (for a single device) change risk level to HIGH
@@ -97,6 +98,7 @@ class RiskLevelEvaluator(
const val MAX_NUMBER_MEDIUM_RISK: Long = 3 // Maximum number of devices with MEDIUM risk until the total risk level is set to high
val relevantTrackingDateForRiskCalculation: LocalDateTime = LocalDateTime.now().minusDays(
RELEVANT_DAYS_RISK_LEVEL) // Fallback Option, if possible use getRelevantTrackingDate() Function
+ val deleteBeforeDate: LocalDateTime = LocalDateTime.now().minusDays(DELETE_SAFE_DEVICES_OLDER_THAN_DAYS)
private val relevantNotificationDate: LocalDateTime = LocalDateTime.now().minusDays(RELEVANT_DAYS_NOTIFICATIONS)
// Default Values: A single tracker gets tracked at least for x minutes until notification is created
From bb4e6d416bf8575f68c1ba3f85c8302002ff43a0 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Fri, 23 Feb 2024 15:35:38 +0100
Subject: [PATCH 084/154] improve connectionState for chipolo devices
---
.../database/models/device/BaseDevice.kt | 16 +---------------
.../database/models/device/DeviceManager.kt | 2 +-
.../models/device/types/SamsungDevice.kt | 4 ++--
.../ui/scan/ScanDistanceFragment.kt | 6 +++++-
.../ui/scan/ScanViewModel.kt | 2 +-
app/src/main/res/values-de/strings.xml | 2 ++
app/src/main/res/values/strings.xml | 2 ++
7 files changed, 14 insertions(+), 20 deletions(-)
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 c548e52d..c64c0dea 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
@@ -68,11 +68,7 @@ data class BaseDevice(
getDeviceName(scanResult),
false,
scanResult.let {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- scanResult.isConnectable
- } else {
- null
- }
+ scanResult.isConnectable
},
scanResult.scanRecord?.getManufacturerSpecificData(76)?.get(2),
LocalDateTime.now(),
@@ -162,16 +158,6 @@ data class BaseDevice(
}
}
- fun getConnectionStateAsString(scanResult: ScanResult): String {
- return when (getConnectionState(scanResult)) {
- ConnectionState.OFFLINE -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.connection_state_offline)
- ConnectionState.PREMATURE_OFFLINE -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.connection_state_premature_offline)
- ConnectionState.OVERMATURE_OFFLINE -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.connection_state_overmature_offline)
- ConnectionState.CONNECTED -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.connection_state_connected)
- ConnectionState.UNKNOWN -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.connection_state_unknown)
- }
- }
-
fun getBatteryStateAsString(scanResult: ScanResult): String {
return when (getBatteryState(scanResult)) {
BatteryState.LOW -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.battery_low)
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 f92d5059..700c8253 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
@@ -12,7 +12,7 @@ object DeviceManager {
val devices = listOf(AirTag, FindMy, AirPods, AppleDevice, SmartTag, SmartTagPlus, Tile, Chipolo)
private val appleDevices = listOf(AirTag, FindMy, AirPods, AppleDevice)
- val savedConnectionStates = listOf(ConnectionState.OVERMATURE_OFFLINE, ConnectionState.UNKNOWN)
+ val unsafeConnectionState = listOf(ConnectionState.OVERMATURE_OFFLINE, ConnectionState.UNKNOWN)
fun getDeviceType(scanResult: ScanResult): DeviceType {
Timber.d("Checking device type for ${scanResult.device.address}")
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SamsungDevice.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SamsungDevice.kt
index a19840ff..3d719410 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SamsungDevice.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/device/types/SamsungDevice.kt
@@ -35,8 +35,8 @@ open class SamsungDevice(open val id: Int) : Device(){
// Twelve Byte:
// 04, 00 --> UWB off,
// 04, 04 --> UWB on
- byteArrayOf((0x13).toByte()),
- byteArrayOf((0xFF).toByte())
+ byteArrayOf((0x10).toByte()),
+ byteArrayOf((0xF8).toByte())
)
.build()
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
index 91407b61..8b0a5ffc 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
@@ -178,11 +178,15 @@ class ScanDistanceFragment : Fragment() {
DeviceType.SAMSUNG,
DeviceType.GALAXY_SMART_TAG,
DeviceType.GALAXY_SMART_TAG_PLUS -> getString(R.string.connection_state_overmature_offline_explanation_samsung)
+ DeviceType.CHIPOLO -> getString(R.string.connection_state_overmature_offline_explanation_chipolo)
else -> getString(R.string.connection_state_overmature_offline_explanation)
}
ConnectionState.CONNECTED -> getString(R.string.connection_state_connected_explanation)
ConnectionState.OFFLINE -> getString(R.string.connection_state_offline_explanation)
- ConnectionState.PREMATURE_OFFLINE -> getString(R.string.connection_state_premature_offline_explanation)
+ ConnectionState.PREMATURE_OFFLINE -> when(deviceType) {
+ DeviceType.CHIPOLO -> getString(R.string.connection_state_premature_offline_explanation_chipolo)
+ else -> getString(R.string.connection_state_premature_offline_explanation)
+ }
ConnectionState.UNKNOWN -> getString(R.string.connection_state_unknown_explanation)
}
}
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 cba58063..44444c6f 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
@@ -98,7 +98,7 @@ class ScanViewModel @Inject constructor(
getPublicKey(it) == uniqueIdentifier
}
- if (BaseDevice.getConnectionState(scanResult) in DeviceManager.savedConnectionStates) {
+ if (BaseDevice.getConnectionState(scanResult) in DeviceManager.unsafeConnectionState) {
// only add possible devices to list
bluetoothDeviceListHighRiskValue.add(scanResult)
} else {
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index d5b266a3..bdffa45d 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -261,10 +261,12 @@
Overmature Offline
Premature Offline
Gerät ist seit mind. 15 Minuten vom Besitzer getrennt
+ Gerät ist seit mind. 30 Minuten vom Besitzer getrennt
Gerät ist seit mind. 8 Stunden vom Besitzer getrennt
Verbunden
Gerät ist verbunden mit Besitzer
Gerät war in den letzten 15 Minuten mit Besitzer verbunden
+ Gerät war in den letzten 30 Minuten mit Besitzer verbunden
Unbekannt
Startet oder Stoppt den Scan
nach Signalstärke
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 67c542f0..e39fd97b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -290,6 +290,7 @@
Overmature Offline
Device is separated from its owner since at least 15 minutes
Device is separated from its owner since at least 8 hours
+ Device is separated from its owner since at least 30 minutes
Premature Offline
Connection to owner in the last 15 minutes
Connected
@@ -368,4 +369,5 @@
Safe Trackers
Legend
Location where a potential tracker was found
+ Connection to owner in the last 30 minutes
From a2598fa9e2518bfe325d6f4a895682e803e32fef Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Fri, 23 Feb 2024 16:05:08 +0100
Subject: [PATCH 085/154] safe all devices no matter in what connectionState.
Hide safe trackers in ui, so that visible behaviour stays the same.
---
.../14.json | 418 ++++++++++++++++++
.../database/AppDatabase.kt | 3 +-
.../database/daos/DeviceDao.kt | 12 +-
.../database/models/device/BaseDevice.kt | 2 +-
.../database/models/device/DeviceManager.kt | 1 +
.../database/repository/DeviceRepository.kt | 25 +-
.../detection/ScanBluetoothWorker.kt | 14 +-
7 files changed, 448 insertions(+), 27 deletions(-)
create mode 100644 app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/14.json
diff --git a/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/14.json b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/14.json
new file mode 100644
index 00000000..b3229a1e
--- /dev/null
+++ b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/14.json
@@ -0,0 +1,418 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 14,
+ "identityHash": "7304689c7fc27bdc81b636a3c17ca82d",
+ "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, `riskLevel` INTEGER NOT NULL DEFAULT 0, `lastCalculatedRiskDate` TEXT, `nextObservationNotification` TEXT, `currentObservationDuration` INTEGER, `safeTracker` INTEGER NOT NULL DEFAULT false)",
+ "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
+ },
+ {
+ "fieldPath": "riskLevel",
+ "columnName": "riskLevel",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "lastCalculatedRiskDate",
+ "columnName": "lastCalculatedRiskDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextObservationNotification",
+ "columnName": "nextObservationNotification",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "currentObservationDuration",
+ "columnName": "currentObservationDuration",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "safeTracker",
+ "columnName": "safeTracker",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "false"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "deviceId"
+ ]
+ },
+ "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, `sensitivity` INTEGER NOT NULL DEFAULT 0)",
+ "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
+ },
+ {
+ "fieldPath": "sensitivity",
+ "columnName": "sensitivity",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "notificationId"
+ ]
+ },
+ "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, `locationId` INTEGER, `mfg` BLOB, `serviceUUIDs` TEXT, `connectionState` TEXT NOT NULL DEFAULT 'UNKNOWN')",
+ "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": "locationId",
+ "columnName": "locationId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "manufacturerData",
+ "columnName": "mfg",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serviceUUIDs",
+ "columnName": "serviceUUIDs",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "connectionState",
+ "columnName": "connectionState",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'UNKNOWN'"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "beaconId"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "feedbackId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "scan",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scanId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `endDate` TEXT, `noDevicesFound` INTEGER, `duration` INTEGER, `isManual` INTEGER NOT NULL, `scanMode` INTEGER NOT NULL, `startDate` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "scanId",
+ "columnName": "scanId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endDate",
+ "columnName": "endDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "noDevicesFound",
+ "columnName": "noDevicesFound",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "duration",
+ "columnName": "duration",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isManual",
+ "columnName": "isManual",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "scanMode",
+ "columnName": "scanMode",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startDate",
+ "columnName": "startDate",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "scanId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "location",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`locationId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `firstDiscovery` TEXT NOT NULL, `lastSeen` TEXT NOT NULL, `longitude` REAL NOT NULL, `latitude` REAL NOT NULL, `accuracy` REAL)",
+ "fields": [
+ {
+ "fieldPath": "locationId",
+ "columnName": "locationId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstDiscovery",
+ "columnName": "firstDiscovery",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastSeen",
+ "columnName": "lastSeen",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longitude",
+ "columnName": "longitude",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latitude",
+ "columnName": "latitude",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accuracy",
+ "columnName": "accuracy",
+ "affinity": "REAL",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "locationId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_location_latitude_longitude",
+ "unique": true,
+ "columnNames": [
+ "latitude",
+ "longitude"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_location_latitude_longitude` ON `${TABLE_NAME}` (`latitude`, `longitude`)"
+ }
+ ],
+ "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, '7304689c7fc27bdc81b636a3c17ca82d')"
+ ]
+ }
+}
\ No newline at end of file
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 314664f7..01f102d3 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
@@ -8,7 +8,7 @@ import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.util.converter.DateTimeConverter
@Database(
- version = 13,
+ version = 14,
entities = [
BaseDevice::class,
Notification::class,
@@ -27,6 +27,7 @@ import de.seemoo.at_tracking_detection.util.converter.DateTimeConverter
AutoMigration(from=10, to=11),
AutoMigration(from=11, to=12),
AutoMigration(from=12, to=13),
+ AutoMigration(from=13, to=14),
],
exportSchema = true
)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/DeviceDao.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/DeviceDao.kt
index 8c21a683..523658a3 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/DeviceDao.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/DeviceDao.kt
@@ -44,25 +44,25 @@ interface DeviceDao {
@Query("UPDATE device SET `ignore` = :state WHERE address = :address")
suspend fun setIgnoreFlag(address: String, state: Boolean)
- @Query("SELECT COUNT(*) FROM device")
+ @Query("SELECT COUNT(*) FROM device WHERE safeTracker == 0")
fun getTotalCount(): Flow
- @Query("SELECT COUNT(*) FROM device WHERE lastSeen >= :since AND notificationSent == 0")
+ @Query("SELECT COUNT(*) FROM device WHERE lastSeen >= :since AND notificationSent == 0 AND safeTracker == 0")
fun getCountNotTracking(since: LocalDateTime): Flow
@Query("SELECT COUNT(*) FROM device WHERE `ignore` == 1")
fun getCountIgnored(): Flow
- @Query("SELECT COUNT(*) FROM device WHERE firstDiscovery >= :since")
+ @Query("SELECT COUNT(*) FROM device WHERE firstDiscovery >= :since AND safeTracker == 0")
fun getTotalCountChange(since: LocalDateTime): Flow
- @Query("SELECT COUNT(*) FROM device WHERE lastSeen >= :since")
+ @Query("SELECT COUNT(*) FROM device WHERE lastSeen >= :since AND safeTracker == 0")
fun getCurrentlyMonitored(since: LocalDateTime): Flow
- @Query("SELECT COUNT(*) FROM device WHERE lastSeen >= :since AND deviceType = :deviceType")
+ @Query("SELECT COUNT(*) FROM device WHERE lastSeen >= :since AND deviceType = :deviceType AND safeTracker == 0")
fun getCountForType(deviceType: String, since: LocalDateTime): Flow
- @Query("SELECT COUNT(*) FROM device WHERE lastSeen >= :since AND (deviceType = :deviceType1 OR deviceType = :deviceType2)")
+ @Query("SELECT COUNT(*) FROM device WHERE lastSeen >= :since AND (deviceType = :deviceType1 OR deviceType = :deviceType2) AND safeTracker == 0")
fun getCountForTypes(deviceType1: String, deviceType2: String, since: LocalDateTime): Flow
@Query("SELECT COUNT(DISTINCT(location.locationId)) FROM device, location, beacon WHERE beacon.locationId = location.locationId AND beacon.deviceAddress = device.address AND beacon.locationId != 0 AND device.address = :deviceAddress AND device.lastSeen >= :since")
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 c64c0dea..31315925 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
@@ -1,7 +1,6 @@
package de.seemoo.at_tracking_detection.database.models.device
import android.bluetooth.le.ScanResult
-import android.os.Build
import androidx.room.*
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
@@ -32,6 +31,7 @@ data class BaseDevice(
@ColumnInfo(name = "lastCalculatedRiskDate") var lastCalculatedRiskDate: LocalDateTime?,
@ColumnInfo(name = "nextObservationNotification") var nextObservationNotification: LocalDateTime?,
@ColumnInfo(name = "currentObservationDuration") var currentObservationDuration: Long?,
+ @ColumnInfo(name = "safeTracker", defaultValue = "false") var safeTracker: Boolean = false,
) {
constructor(
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 700c8253..2444a0f0 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
@@ -12,6 +12,7 @@ object DeviceManager {
val devices = listOf(AirTag, FindMy, AirPods, AppleDevice, SmartTag, SmartTagPlus, Tile, Chipolo)
private val appleDevices = listOf(AirTag, FindMy, AirPods, AppleDevice)
+ val savedConnectionStates = enumValues().toList()
val unsafeConnectionState = listOf(ConnectionState.OVERMATURE_OFFLINE, ConnectionState.UNKNOWN)
fun getDeviceType(scanResult: ScanResult): DeviceType {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt
index ba00bb00..2b529b15 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/DeviceRepository.kt
@@ -27,7 +27,7 @@ class DeviceRepository @Inject constructor(private val deviceDao: DeviceDao) {
fun updateRiskLevelCache(deviceAddress: String, riskLevel: Int, lastCalculatedRiskDate: LocalDateTime)
= deviceDao.updateRiskLevelCache(deviceAddress, riskLevel, lastCalculatedRiskDate)
- fun trackingDevicesSinceCount(since: LocalDateTime) = deviceDao.trackingDevicesCount(since)
+ // fun trackingDevicesSinceCount(since: LocalDateTime) = deviceDao.trackingDevicesCount(since)
val totalCount: Flow = deviceDao.getTotalCount()
@@ -37,10 +37,9 @@ class DeviceRepository @Inject constructor(private val deviceDao: DeviceDao) {
fun devicesCurrentlyMonitored(since: LocalDateTime): Flow =
deviceDao.getCurrentlyMonitored(since)
- fun deviceCountSince(since: LocalDateTime): Flow =
- deviceDao.getCurrentlyMonitored(since)
+ // fun deviceCountSince(since: LocalDateTime): Flow = deviceDao.getCurrentlyMonitored(since)
- val ignoredDevices: Flow> = deviceDao.getIgnored()
+ // val ignoredDevices: Flow> = deviceDao.getIgnored()
val ignoredDevicesSync: List = deviceDao.getIgnoredSync()
@@ -59,14 +58,14 @@ class DeviceRepository @Inject constructor(private val deviceDao: DeviceDao) {
fun getDevicesOlderThanWithoutNotifications(since: LocalDateTime): List = deviceDao.getDevicesOlderThanWithoutNotifications(since)
- @WorkerThread
- suspend fun getDeviceBeaconsSince(dateTime: String?): List {
- return if (dateTime != null) {
- deviceDao.getDeviceBeaconsSince(LocalDateTime.parse(dateTime))
- } else {
- deviceDao.getDeviceBeacons()
- }
- }
+// @WorkerThread
+// suspend fun getDeviceBeaconsSince(dateTime: String?): List {
+// return if (dateTime != null) {
+// deviceDao.getDeviceBeaconsSince(LocalDateTime.parse(dateTime))
+// } else {
+// deviceDao.getDeviceBeacons()
+// }
+// }
suspend fun getDeviceBeaconsSinceDate(dateTime: LocalDateTime?): List {
return if (dateTime != null) {
@@ -92,7 +91,7 @@ class DeviceRepository @Inject constructor(private val deviceDao: DeviceDao) {
}
@WorkerThread
- suspend fun deleteDevice(baseDevice: BaseDevice) {
+ suspend fun delete(baseDevice: BaseDevice) {
deviceDao.deleteDevice(baseDevice)
}
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 1c819d5b..4b58fb9b 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
@@ -318,12 +318,14 @@ class ScanBluetoothWorker @AssistedInject constructor(
// Check if ConnectionState qualifies Device to be saved
// Only Save when Device is offline long enough
- when(BaseDevice.getConnectionState(scanResult)){
- ConnectionState.OVERMATURE_OFFLINE -> {}
- // ConnectionState.OFFLINE -> {}
- // ConnectionState.PREMATURE_OFFLINE -> {}
- ConnectionState.UNKNOWN -> {}
- else -> return null
+ if (BaseDevice.getConnectionState(scanResult) !in DeviceManager.savedConnectionStates) {
+ Timber.d("Device not in a saved connection state... Skipping!")
+ return null
+ }
+
+ if (BaseDevice.getConnectionState(scanResult) !in DeviceManager.unsafeConnectionState) {
+ Timber.d("Device is safe and will be hidden to the user!")
+ device.safeTracker = true
}
Timber.d("Add new Device to the database!")
From 1ee5ee4b91a417ac0a2fdb65d5a0809cf4815ed6 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 25 Feb 2024 15:34:16 +0100
Subject: [PATCH 086/154] log when and how many devices are getting deleted,
small optimizations in deleting process
---
.../detection/TrackingDetectorWorker.kt | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
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 0d63cc20..39d69fec 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
@@ -122,13 +122,23 @@ class TrackingDetectorWorker @AssistedInject constructor(
private suspend fun deleteOldAndSafeTrackers() {
// Delete old devices and beacons from the database
- Timber.d("Deleting old and safe Trackers")
+ Timber.d("Start deleting old and safe Trackers")
val deleteSafeTrackersBefore = RiskLevelEvaluator.deleteBeforeDate
val beaconsToBeDeleted = beaconRepository.getBeaconsOlderThanWithoutNotifications(deleteSafeTrackersBefore)
- beaconRepository.deleteBeacons(beaconsToBeDeleted)
+ if (beaconsToBeDeleted.isNotEmpty()) {
+ Timber.d("Deleting ${beaconsToBeDeleted.size} beacons")
+ beaconRepository.deleteBeacons(beaconsToBeDeleted)
+ Timber.d("Deleting Beacons successful")
+ }
val devicesToBeDeleted = deviceRepository.getDevicesOlderThanWithoutNotifications(deleteSafeTrackersBefore)
- deviceRepository.deleteDevices(devicesToBeDeleted)
- Timber.d("Deleting successful")
+ if (devicesToBeDeleted.isNotEmpty()) {
+ Timber.d("Deleting ${devicesToBeDeleted.size} devices")
+ deviceRepository.deleteDevices(devicesToBeDeleted)
+ Timber.d("Deleting Devices successful")
+ }
+ if (beaconsToBeDeleted.isEmpty() && devicesToBeDeleted.isEmpty()) {
+ Timber.d("No old devices or beacons to delete")
+ }
}
companion object {
From 4a358edd08e71b7a43cb230dfc2f2ed3705f5b1f Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 5 Mar 2024 16:47:02 +0100
Subject: [PATCH 087/154] upgrade gradle to 8.3.0 and upgrade some dependencies
---
app/build.gradle | 14 +++++++-------
build.gradle | 2 +-
gradle/wrapper/gradle-wrapper.properties | 2 +-
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index ce1199cb..9c54d6e2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -117,8 +117,8 @@ dependencies {
debugImplementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.5'
implementation "com.google.dagger:hilt-android:$hilt_version"
- implementation 'androidx.hilt:hilt-work:1.1.0'
- implementation 'androidx.hilt:hilt-navigation-fragment:1.1.0'
+ implementation 'androidx.hilt:hilt-work:1.2.0'
+ implementation 'androidx.hilt:hilt-navigation-fragment:1.2.0'
implementation 'com.github.AppIntro:AppIntro:6.1.0'
@@ -134,7 +134,7 @@ dependencies {
kapt "com.google.dagger:hilt-compiler:$hilt_version"
- kapt 'androidx.hilt:hilt-compiler:1.1.0'
+ kapt 'androidx.hilt:hilt-compiler:1.2.0'
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
@@ -159,15 +159,15 @@ dependencies {
// Integration with activities
implementation 'androidx.activity:activity-compose:1.8.2'
// Compose Material Design
- implementation 'androidx.compose.material:material:1.6.1'
+ implementation 'androidx.compose.material:material:1.6.2'
// Animations
- implementation 'androidx.compose.animation:animation:1.6.1'
+ implementation 'androidx.compose.animation:animation:1.6.2'
// Tooling support (Previews, etc.)
- implementation 'androidx.compose.ui:ui-tooling:1.6.1'
+ implementation 'androidx.compose.ui:ui-tooling:1.6.2'
// Integration with ViewModels
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
// UI Tests
- androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.1'
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.2'
// When using a MDC theme
implementation "com.google.android.material:compose-theme-adapter:1.2.1"
diff --git a/build.gradle b/build.gradle
index dab908f3..d8d10c09 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@ buildscript {
}
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.2.2'
+ classpath 'com.android.tools.build:gradle:8.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index b4af7876..6b85a69d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Wed Aug 02 18:45:44 CEST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
From 335112a62b6faf95fffb3f31982ac5af3a21c090 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 5 Mar 2024 17:06:16 +0100
Subject: [PATCH 088/154] remove Battery from Distance Scan
remove Sorting Options from Manual Scan
remove Potential Trackers Label from Manual Scan
---
.../ui/scan/ScanDistanceFragment.kt | 78 ++++----
.../ui/scan/ScanFragment.kt | 63 +++---
app/src/main/res/layout/fragment_scan.xml | 184 +++++++++---------
.../res/layout/fragment_scan_distance.xml | 81 ++++----
4 files changed, 193 insertions(+), 213 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
index 8b0a5ffc..fac7f1da 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
@@ -6,13 +6,10 @@ import android.bluetooth.le.ScanResult
import android.os.Bundle
import android.os.Handler
import android.os.Looper
-import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.widget.Toast
import androidx.core.animation.addListener
-import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -22,7 +19,6 @@ import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getBatteryState
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getBatteryStateAsString
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getConnectionState
-import de.seemoo.at_tracking_detection.util.ble.BLEScanner
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
import de.seemoo.at_tracking_detection.database.models.device.BatteryState
import de.seemoo.at_tracking_detection.database.models.device.ConnectionState
@@ -30,6 +26,7 @@ import de.seemoo.at_tracking_detection.database.models.device.DeviceManager
import de.seemoo.at_tracking_detection.database.models.device.DeviceType
import de.seemoo.at_tracking_detection.databinding.FragmentScanDistanceBinding
import de.seemoo.at_tracking_detection.util.Utility
+import de.seemoo.at_tracking_detection.util.ble.BLEScanner
import timber.log.Timber
class ScanDistanceFragment : Fragment() {
@@ -69,8 +66,9 @@ class ScanDistanceFragment : Fragment() {
val displayedConnectionQuality = (connectionQuality * 100).toInt()
viewModel.connectionQuality.postValue(displayedConnectionQuality)
- setDeviceType(requireContext(), deviceType)
- setBattery(requireContext(), batteryState)
+ binding.deviceTypeText.text = DeviceType.userReadableName(deviceType)
+
+ // setBattery(requireContext(), batteryState)
setHeight(connectionQuality)
if (viewModel.isFirstScanCallback.value as Boolean) {
@@ -104,18 +102,18 @@ class ScanDistanceFragment : Fragment() {
binding.connectionStateLayout.visibility = View.VISIBLE
binding.scanExplanationLayout.visibility = View.VISIBLE
binding.deviceNotFound.visibility = View.GONE
- if (showBattery) {
- binding.batteryLayout.visibility = View.VISIBLE
- } else {
- binding.batteryLayout.visibility = View.GONE
- }
+// if (showBattery) {
+// binding.batteryLayout.visibility = View.VISIBLE
+// } else {
+// binding.batteryLayout.visibility = View.GONE
+// }
}
private fun showSearchMessage() {
binding.scanResultLoadingBar.visibility = View.VISIBLE
binding.searchingForDevice.visibility = View.VISIBLE
binding.connectionQuality.visibility = View.GONE
- binding.batteryLayout.visibility = View.GONE
+ // binding.batteryLayout.visibility = View.GONE
binding.scanExplanationLayout.visibility = View.GONE
binding.deviceTypeLayout.visibility = View.GONE
binding.connectionStateLayout.visibility = View.GONE
@@ -127,7 +125,7 @@ class ScanDistanceFragment : Fragment() {
binding.searchingForDevice.visibility = View.GONE
binding.connectionQuality.visibility = View.GONE
binding.scanExplanationLayout.visibility = View.GONE
- binding.batteryLayout.visibility = View.GONE
+ // binding.batteryLayout.visibility = View.GONE
binding.deviceTypeLayout.visibility = View.GONE
binding.connectionStateLayout.visibility = View.GONE
binding.deviceNotFound.visibility = View.VISIBLE
@@ -155,22 +153,16 @@ class ScanDistanceFragment : Fragment() {
}
}
- private fun setBattery(context: Context, batteryState: BatteryState) {
- binding.batteryLayout.visibility = View.VISIBLE
- when(batteryState) {
- BatteryState.FULL -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_full_24))
- BatteryState.MEDIUM -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_medium_24))
- BatteryState.LOW -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_low_24))
- BatteryState.VERY_LOW -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_very_low_24))
- else -> binding.batteryLayout.visibility = View.GONE
- }
- }
-
- private fun setDeviceType(context: Context, deviceType: DeviceType) {
- val drawable = ContextCompat.getDrawable(context, DeviceType.getImageDrawable(deviceType))
- binding.deviceTypeSymbol.setImageDrawable(drawable)
- binding.deviceTypeText.text = DeviceType.userReadableName(deviceType)
- }
+// private fun setBattery(context: Context, batteryState: BatteryState) {
+// binding.batteryLayout.visibility = View.VISIBLE
+// when(batteryState) {
+// BatteryState.FULL -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_full_24))
+// BatteryState.MEDIUM -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_medium_24))
+// BatteryState.LOW -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_low_24))
+// BatteryState.VERY_LOW -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_very_low_24))
+// else -> binding.batteryLayout.visibility = View.GONE
+// }
+// }
private fun getConnectionStateExplanation(connectionState: ConnectionState, deviceType: DeviceType): String {
return when (connectionState) {
@@ -254,20 +246,20 @@ class ScanDistanceFragment : Fragment() {
// toast.show()
// }
- val batterySymbol = binding.batterySymbol
- batterySymbol.setOnClickListener {
- val text = when (viewModel.batteryState.value as BatteryState){
- BatteryState.FULL -> R.string.battery_full
- BatteryState.MEDIUM -> R.string.battery_medium
- BatteryState.VERY_LOW -> R.string.battery_very_low
- BatteryState.LOW -> R.string.battery_low
- else -> R.string.battery_unknown
- }
- val duration = Toast.LENGTH_SHORT
-
- val toast = Toast.makeText(requireContext(), text, duration) // in Activity
- toast.show()
- }
+// val batterySymbol = binding.batterySymbol
+// batterySymbol.setOnClickListener {
+// val text = when (viewModel.batteryState.value as BatteryState){
+// BatteryState.FULL -> R.string.battery_full
+// BatteryState.MEDIUM -> R.string.battery_medium
+// BatteryState.VERY_LOW -> R.string.battery_very_low
+// BatteryState.LOW -> R.string.battery_low
+// else -> R.string.battery_unknown
+// }
+// val duration = Toast.LENGTH_SHORT
+//
+// val toast = Toast.makeText(requireContext(), text, duration) // in Activity
+// toast.show()
+// }
return binding.root
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
index e8203afc..32f69694 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
@@ -9,7 +9,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
-import android.widget.TextView
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -71,20 +70,20 @@ class ScanFragment : Fragment() {
scanViewModel.bluetoothDeviceListHighRisk.postValue(ArrayList(bluetoothDeviceListHighRiskValue))
scanViewModel.bluetoothDeviceListLowRisk.postValue(ArrayList(bluetoothDeviceListLowRiskValue))
- if (view != null) {
- val sortBySignalStrength = requireView().findViewById(R.id.sort_option_signal_strength)
- val sortByDetectionOrder = requireView().findViewById(R.id.sort_option_order_detection)
- val sortByAddress = requireView().findViewById(R.id.sort_option_address)
-
- val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
-
- when(it) {
- SortingOrder.SIGNAL_STRENGTH -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
- SortingOrder.DETECTION_ORDER -> scanViewModel.changeColorOf(sortOptions, sortByDetectionOrder)
- SortingOrder.ADDRESS -> scanViewModel.changeColorOf(sortOptions, sortByAddress)
- else -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
- }
- }
+// if (view != null) {
+// val sortBySignalStrength = requireView().findViewById(R.id.sort_option_signal_strength)
+// val sortByDetectionOrder = requireView().findViewById(R.id.sort_option_order_detection)
+// val sortByAddress = requireView().findViewById(R.id.sort_option_address)
+//
+// val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
+//
+// when(it) {
+// SortingOrder.SIGNAL_STRENGTH -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
+// SortingOrder.DETECTION_ORDER -> scanViewModel.changeColorOf(sortOptions, sortByDetectionOrder)
+// SortingOrder.ADDRESS -> scanViewModel.changeColorOf(sortOptions, sortByAddress)
+// else -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
+// }
+// }
}
@@ -110,23 +109,23 @@ class ScanFragment : Fragment() {
}
}
- val sortBySignalStrength = view.findViewById(R.id.sort_option_signal_strength)
- val sortByDetectionOrder = view.findViewById(R.id.sort_option_order_detection)
- val sortByAddress = view.findViewById(R.id.sort_option_address)
-
- val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
-
- scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
-
- sortBySignalStrength.setOnClickListener {
- scanViewModel.sortingOrder.postValue(SortingOrder.SIGNAL_STRENGTH)
- }
- sortByDetectionOrder.setOnClickListener {
- scanViewModel.sortingOrder.postValue(SortingOrder.DETECTION_ORDER)
- }
- sortByAddress.setOnClickListener {
- scanViewModel.sortingOrder.postValue(SortingOrder.ADDRESS)
- }
+// val sortBySignalStrength = view.findViewById(R.id.sort_option_signal_strength)
+// val sortByDetectionOrder = view.findViewById(R.id.sort_option_order_detection)
+// val sortByAddress = view.findViewById(R.id.sort_option_address)
+//
+// val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
+//
+// scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
+//
+// sortBySignalStrength.setOnClickListener {
+// scanViewModel.sortingOrder.postValue(SortingOrder.SIGNAL_STRENGTH)
+// }
+// sortByDetectionOrder.setOnClickListener {
+// scanViewModel.sortingOrder.postValue(SortingOrder.DETECTION_ORDER)
+// }
+// sortByAddress.setOnClickListener {
+// scanViewModel.sortingOrder.postValue(SortingOrder.ADDRESS)
+// }
}
override fun onStart() {
diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml
index f8bc5ed2..8e44537a 100644
--- a/app/src/main/res/layout/fragment_scan.xml
+++ b/app/src/main/res/layout/fragment_scan.xml
@@ -25,86 +25,86 @@
android:layout_height="match_parent"
tools:context=".ui.scan.ScanFragment">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toTopOf="parent" />
-
+
+
+
+
+
+
+
+
+
+
+
-
-
@@ -82,7 +71,7 @@
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
- bind:layout_constraintBottom_toTopOf="@id/battery_layout"
+ bind:layout_constraintBottom_toTopOf="@id/connection_state_layout"
bind:layout_constraintTop_toBottomOf="@id/connection_quality"
bind:layout_constraintEnd_toEndOf="parent"
bind:layout_constraintStart_toStartOf="parent">
@@ -102,38 +91,38 @@
bind:layout_constraintHorizontal_chainStyle="packed"/>
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From ee89dd4119fa33d6c5f0709f82dea0b96cbf1811 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 6 Mar 2024 10:43:28 +0100
Subject: [PATCH 089/154] Show No trackers found since installation date,
instead of last 14 days
---
.../ui/dashboard/RiskCardViewModel.kt | 43 ++++++++++++++++---
.../util/risk/RiskLevelEvaluator.kt | 2 +-
app/src/main/res/values-de/strings.xml | 4 +-
app/src/main/res/values-ja/strings.xml | 2 +-
app/src/main/res/values/strings.xml | 6 +--
5 files changed, 45 insertions(+), 12 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskCardViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskCardViewModel.kt
index 345f85d6..14514247 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskCardViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskCardViewModel.kt
@@ -1,6 +1,8 @@
package de.seemoo.at_tracking_detection.ui.dashboard
+import android.content.Context
import android.content.SharedPreferences
+import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@@ -12,8 +14,10 @@ import de.seemoo.at_tracking_detection.util.risk.RiskLevel
import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
import java.text.DateFormat
import java.time.LocalDateTime
+import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
+import java.util.Date
import javax.inject.Inject
@HiltViewModel
@@ -29,7 +33,7 @@ class RiskCardViewModel @Inject constructor(
var trackersFoundModel: MutableLiveData = MutableLiveData()
var lastUpdateModel: MutableLiveData = MutableLiveData()
var lastDiscoveryModel: MutableLiveData = MutableLiveData()
- var dismissSurveyInformation: MutableLiveData = MutableLiveData(SharedPrefs.dismissSurveyInformation)
+ private var dismissSurveyInformation: MutableLiveData = MutableLiveData(SharedPrefs.dismissSurveyInformation)
private var lastScan: LocalDateTime? = null
private var sharedPreferencesListener: SharedPreferences.OnSharedPreferenceChangeListener =
@@ -53,7 +57,7 @@ class RiskCardViewModel @Inject constructor(
updateRiskLevel()
}
- fun updateLastUpdateModel() {
+ private fun updateLastUpdateModel() {
val context = ATTrackingDetectionApplication.getAppContext()
val lastScanString = if (lastScan != null) {
@@ -74,6 +78,8 @@ class RiskCardViewModel @Inject constructor(
val dateFormat = DateFormat.getDateTimeInstance()
val lastDiscoveryDate = riskLevelEvaluator.getLastTrackerDiscoveryDate()
val lastDiscoveryDateString = dateFormat.format(lastDiscoveryDate)
+ val earliestTrackingDate = getEarliestTrackingDate()
+ val earliestTrackingDateString = dateFormat.format(earliestTrackingDate)
val totalAlerts = riskLevelEvaluator.getNumberRelevantTrackers()
updateLastUpdateModel()
@@ -86,7 +92,7 @@ class RiskCardViewModel @Inject constructor(
riskColor = ContextCompat.getColor(context, R.color.risk_low)
trackersFoundModel.postValue(RiskRowViewModel(
- context.getString(R.string.no_trackers_found, RiskLevelEvaluator.RELEVANT_HOURS_TRACKING),
+ context.getString(R.string.no_trackers_found, earliestTrackingDateString),
ContextCompat.getDrawable(context, R.drawable.ic_baseline_location_on_24)!!
))
lastDiscoveryModel.postValue(RiskRowViewModel(
@@ -105,7 +111,7 @@ class RiskCardViewModel @Inject constructor(
context.getString(
R.string.found_x_trackers,
totalAlerts,
- RiskLevelEvaluator.RELEVANT_HOURS_TRACKING
+ RiskLevelEvaluator.RELEVANT_DAYS_RISK_LEVEL
),
ContextCompat.getDrawable(context, R.drawable.ic_baseline_location_on_24)!!
))
@@ -127,7 +133,7 @@ class RiskCardViewModel @Inject constructor(
context.getString(
R.string.found_x_trackers,
totalAlerts,
- RiskLevelEvaluator.RELEVANT_HOURS_TRACKING
+ RiskLevelEvaluator.RELEVANT_DAYS_RISK_LEVEL
),
ContextCompat.getDrawable(context, R.drawable.ic_baseline_location_on_24)!!
))
@@ -139,4 +145,31 @@ class RiskCardViewModel @Inject constructor(
}
}
}
+
+ private fun getEarliestTrackingDate(): Date {
+ val context = ATTrackingDetectionApplication.getAppContext()
+ val installDate = getInstallDate(context)
+ val oldestDatePossible = RiskLevelEvaluator.relevantTrackingDateForRiskCalculation
+ val oldestDatePossibleAsDate = Date.from(oldestDatePossible.atZone(ZoneId.systemDefault()).toInstant())
+
+ return if (installDate != null && installDate > oldestDatePossibleAsDate) {
+ installDate
+ } else {
+ oldestDatePossibleAsDate
+ }
+ }
+
+ private fun getInstallDate(context: Context): Date? {
+ try {
+ val packageManager = context.packageManager
+ val packageInfo = packageManager.getPackageInfo(context.packageName, 0)
+ val installTimeMillis = packageInfo.firstInstallTime
+
+ // Convert milliseconds to Date
+ return Date(installTimeMillis)
+ } catch (e: PackageManager.NameNotFoundException) {
+ e.printStackTrace()
+ }
+ return null
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
index 980dfead..042db9cb 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
@@ -89,7 +89,7 @@ class RiskLevelEvaluator(
/** The number of days that we use to calculate the risk **/
const val RELEVANT_HOURS_TRACKING: Long = 24 // Only consider beacons in the last x hours, default value, can be overwritten in the specific device properties
private const val DELETE_SAFE_DEVICES_OLDER_THAN_DAYS: Long = 30 // Delete devices that have been seen last more than x days ago
- private const val RELEVANT_DAYS_RISK_LEVEL: Long = 14
+ const val RELEVANT_DAYS_RISK_LEVEL: Long = 14
private const val MINUTES_UNTIL_CACHE_IS_UPDATED: Long = 15
private const val NUMBER_OF_NOTIFICATIONS_FOR_HIGH_RISK: Long = 2 // After x MEDIUM risk notifications (for a single device) change risk level to HIGH
private const val RELEVANT_DAYS_NOTIFICATIONS: Long = 5 // After MEDIUM risk notifications in the last x days (for a single device) change risk level to HIGH
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index bdffa45d..6ae9ce3c 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -152,8 +152,8 @@
Kein Risiko
Mittleres Risiko
Hohes Risiko
- Keine Tracker gefunden in den letzten %d Stunden
- %d Tracker gefunden in den letzten %d Stunden
+ Keine Tracker gefunden seit: %s
+ %d Tracker gefunden in den letzten %d Tagen
Letzter Scan: %s
Letzter Tracker gefunden am %s
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 2e364952..844a9e26 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -129,7 +129,7 @@
問題なし
注意
危険
- 過去 %d 日間にタグは見つかりませんでした
+ それ以来、トラッカーは見つかっていない:%s
過去 %d 日間に %d つのタグが検出されました
最後のスキャン日時: %s
最後に見つかったタグは %s です
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e39fd97b..2244159d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -168,7 +168,7 @@
Check out our Twitter account
Twitter
Scan Result Item
- No tracking devices were found. You\'re safe!\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), Galaxy SmartTags, Chipolos and Tiles.
Failed to connect to device
Yes
Bluetooth must be turned on to scan for bluetooth devices.
@@ -176,8 +176,8 @@
No risk
Medium risk
High risk
- No trackers found during the last %d hours
- Detected %d tracker during the last %d hours
+ No trackers found since: %s
+ Detected %d tracker during the last %d days
Last scan: %s
Last tracker discovered at %s
How does AirGuard work?
From a014d7622d0d8c1feb30f95642871d0de62abd99 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 6 Mar 2024 10:52:10 +0100
Subject: [PATCH 090/154] fix string showing wrong device type in feedback View
---
app/src/main/res/values-de/strings.xml | 2 +-
app/src/main/res/values-ja/strings.xml | 2 +-
app/src/main/res/values/strings.xml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 6ae9ce3c..7d7906b0 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -45,7 +45,7 @@
Benachrichtigungen für dieses Gerät deaktivieren
Fehlalarm
Diese Benachrichtigung als Fehlalarm markieren
- Wo war das "Find My"-Gerät versteckt?
+ Wo war das Gerät versteckt?
Rucksack
Kleidung
Auto
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 844a9e26..4971f25e 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -40,7 +40,7 @@
誤報
この通知を誤報としてマークする
一部のタグは位置データなしで検知されたため、表示されません
- Find Myデバイスはどこに隠されていましたか?
+ 装置はどこに隠されていたのか?
リュックサック
服
車
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2244159d..62dff339 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -50,7 +50,7 @@
False Alarm
Mark this notification as false alarm
Some beacons were captured without any location data and are therefore not displayed.
- Where was the Find My device hidden?
+ Where was the Tracker you found hidden?
Backpack
Clothes
Car
From beecb0b11ef9f86aee3ca7e21704aa4c7d65b426 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 6 Mar 2024 12:23:05 +0100
Subject: [PATCH 091/154] Redesign Feedback, Fix Crashes in Feedback
---
.../ui/feedback/FeedbackFragment.kt | 98 +++++++++++++------
.../ui/feedback/LocationItem.kt | 7 ++
.../res/drawable/ic_baseline_backpack_24.xml | 5 +
.../drawable/ic_baseline_bike_scooter_24.xml | 9 ++
.../res/drawable/ic_baseline_cancel_24.xml | 5 +
.../main/res/drawable/ic_baseline_car_24.xml | 5 +
.../drawable/ic_baseline_more_horiz_24.xml | 5 +
.../res/drawable/ic_baseline_person_24.xml | 7 ++
app/src/main/res/layout/fragment_feedback.xml | 12 ++-
.../res/layout/item_feedback_selection.xml | 32 ++++++
10 files changed, 151 insertions(+), 34 deletions(-)
create mode 100644 app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/LocationItem.kt
create mode 100644 app/src/main/res/drawable/ic_baseline_backpack_24.xml
create mode 100644 app/src/main/res/drawable/ic_baseline_bike_scooter_24.xml
create mode 100644 app/src/main/res/drawable/ic_baseline_cancel_24.xml
create mode 100644 app/src/main/res/drawable/ic_baseline_car_24.xml
create mode 100644 app/src/main/res/drawable/ic_baseline_more_horiz_24.xml
create mode 100644 app/src/main/res/drawable/ic_baseline_person_24.xml
create mode 100644 app/src/main/res/layout/item_feedback_selection.xml
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/FeedbackFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/FeedbackFragment.kt
index 2ef78067..a0561a6b 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/FeedbackFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/FeedbackFragment.kt
@@ -1,18 +1,18 @@
package de.seemoo.at_tracking_detection.ui.feedback
import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.navArgs
-import com.google.android.material.chip.Chip
-import com.google.android.material.chip.ChipGroup
+import com.google.android.material.card.MaterialCardView
import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.databinding.FragmentFeedbackBinding
@@ -24,6 +24,9 @@ class FeedbackFragment : Fragment() {
private val safeArgs: FeedbackFragmentArgs by navArgs()
+ // This gives the option to highlight the selected feedback location in future updates
+ private var selectedLocationCard: MaterialCardView? = null
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@@ -38,36 +41,73 @@ class FeedbackFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- val locationChipGroup = view.findViewById(R.id.feedback_location_chip_group)
- val locations = arrayOf(
- R.string.feedback_location_backpack, R.string.feedback_location_clothes,
- R.string.feedback_location_car, R.string.feedback_location_bike,
- R.string.feedback_location_other, R.string.feedback_location_not_found
+ val locations = listOf(
+ LocationItem(getString(R.string.feedback_location_backpack), "Bag", R.drawable.ic_baseline_backpack_24),
+ LocationItem(getString(R.string.feedback_location_clothes), "Clothes", R.drawable.ic_baseline_person_24),
+ LocationItem(getString(R.string.feedback_location_car), "Car", R.drawable.ic_baseline_car_24),
+ LocationItem(getString(R.string.feedback_location_bike), "Bike", R.drawable.ic_baseline_bike_scooter_24),
+ LocationItem(getString(R.string.feedback_location_other), "Other", R.drawable.ic_baseline_more_horiz_24),
+ LocationItem(getString(R.string.feedback_location_not_found), "NotFound", R.drawable.ic_baseline_cancel_24)
)
- for (location in locations) {
- val chip =
- layoutInflater.inflate(
- R.layout.include_choice_chip,
- locationChipGroup,
- false
- ) as Chip
- chip.setText(location)
- feedbackViewModel.location.observe(viewLifecycleOwner) {
- if (it == getString(location)) {
- chip.isChecked = true
- }
- }
- chip.setOnClickListener {
- feedbackViewModel.location.postValue(getString(location))
+
+ val locationsLinearLayout = LinearLayout(context)
+ locationsLinearLayout.orientation = LinearLayout.VERTICAL
+ locationsLinearLayout.layoutParams = LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ ).apply {
+ topMargin = 16
+ }
+
+ val locationLayout = view.findViewById(R.id.feedback_location_layout)
+
+ for (locationItem in locations) {
+ val locationCard = MaterialCardView(context)
+
+ val layout = LayoutInflater.from(context).inflate(R.layout.item_feedback_selection, null)
+ val text = layout.findViewById(R.id.text)
+ val icon = layout.findViewById(R.id.icon)
+
+ icon.setImageResource(locationItem.imageResId)
+ text.text = locationItem.visibleString
+
+ locationCard.addView(layout)
+
+ locationCard.setOnClickListener {
+ // Clear previously selected location
+ selectedLocationCard?.isChecked = false
+
+ // Mark the current location as selected
+ locationCard.isChecked = true
+ selectedLocationCard = locationCard
+
+ // Update ViewModel with selected location
+ feedbackViewModel.location.postValue(locationItem.backendString)
+
+ // Show a Toast message indicating success
Toast.makeText(requireContext(), R.string.feedback_success, Toast.LENGTH_SHORT).show()
- Handler(Looper.getMainLooper()).postDelayed({
- requireActivity().supportFragmentManager.beginTransaction().remove(this).commit()
- // Show a Toast message indicating success
- }, 200)
}
- locationChipGroup.addView(chip)
+
+ // Set layout params for location card
+ locationCard.layoutParams = LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ ).apply {
+ topMargin = 16
+ }
+
+ locationsLinearLayout.addView(locationCard)
+
+ // Check if this location is already selected
+ if (locationItem.backendString == feedbackViewModel.location.value) {
+ // Mark the current location as selected
+ locationCard.isChecked = true
+ selectedLocationCard = locationCard
+ }
}
+
+ locationLayout.addView(locationsLinearLayout)
}
override fun onPause() {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/LocationItem.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/LocationItem.kt
new file mode 100644
index 00000000..d62465ef
--- /dev/null
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/LocationItem.kt
@@ -0,0 +1,7 @@
+package de.seemoo.at_tracking_detection.ui.feedback
+
+data class LocationItem(
+ val visibleString: String,
+ val backendString: String,
+ val imageResId: Int
+)
diff --git a/app/src/main/res/drawable/ic_baseline_backpack_24.xml b/app/src/main/res/drawable/ic_baseline_backpack_24.xml
new file mode 100644
index 00000000..cf05a339
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_backpack_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_bike_scooter_24.xml b/app/src/main/res/drawable/ic_baseline_bike_scooter_24.xml
new file mode 100644
index 00000000..1021d87a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_bike_scooter_24.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_cancel_24.xml b/app/src/main/res/drawable/ic_baseline_cancel_24.xml
new file mode 100644
index 00000000..be783902
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_cancel_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_car_24.xml b/app/src/main/res/drawable/ic_baseline_car_24.xml
new file mode 100644
index 00000000..3ca75354
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_car_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_more_horiz_24.xml b/app/src/main/res/drawable/ic_baseline_more_horiz_24.xml
new file mode 100644
index 00000000..ed06756c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_more_horiz_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_person_24.xml b/app/src/main/res/drawable/ic_baseline_person_24.xml
new file mode 100644
index 00000000..8f88bd2e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_person_24.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_feedback.xml b/app/src/main/res/layout/fragment_feedback.xml
index 0d9e7f4f..a930296f 100644
--- a/app/src/main/res/layout/fragment_feedback.xml
+++ b/app/src/main/res/layout/fragment_feedback.xml
@@ -1,6 +1,5 @@
@@ -29,11 +28,14 @@
android:textAppearance="?attr/textAppearanceHeadline6"
android:textSize="20sp" />
-
+ android:orientation="vertical"
+ android:layout_marginTop="16dp">
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_feedback_selection.xml b/app/src/main/res/layout/item_feedback_selection.xml
new file mode 100644
index 00000000..751885f9
--- /dev/null
+++ b/app/src/main/res/layout/item_feedback_selection.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
From adf6e036abc8ab513a5c4a4e5765d0e7cff1bef1 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 6 Mar 2024 14:34:08 +0100
Subject: [PATCH 092/154] make Feedback Button more visible, also improve some
padding
---
.../ui/feedback/FeedbackFragment.kt | 2 +-
app/src/main/res/layout/fragment_tracking.xml | 43 +++++++++----------
.../main/res/layout/include_tracking_tile.xml | 1 +
.../res/layout/item_feedback_selection.xml | 1 +
app/src/main/res/values/colors.xml | 1 +
5 files changed, 25 insertions(+), 23 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/FeedbackFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/FeedbackFragment.kt
index a0561a6b..3159da36 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/FeedbackFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/feedback/FeedbackFragment.kt
@@ -57,7 +57,7 @@ class FeedbackFragment : Fragment() {
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
- topMargin = 16
+ topMargin = 24
}
val locationLayout = view.findViewById(R.id.feedback_location_layout)
diff --git a/app/src/main/res/layout/fragment_tracking.xml b/app/src/main/res/layout/fragment_tracking.xml
index 5573b90b..25bba1bb 100644
--- a/app/src/main/res/layout/fragment_tracking.xml
+++ b/app/src/main/res/layout/fragment_tracking.xml
@@ -82,6 +82,21 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/map_container">
+
-
-
#325B76
#434343
#1B9CAE
+ #FF3D2F
\ No newline at end of file
From bcea3bf2ecfa25e920d62fe3a3787c2b5e5c4162 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 6 Mar 2024 15:38:43 +0100
Subject: [PATCH 093/154] add explanation text to device view that explains
that some devices change their key
---
.../ui/tracking/TrackingFragment.kt | 6 ++++++
.../ui/tracking/TrackingViewModel.kt | 9 +++++----
.../at_tracking_detection/util/Utility.kt | 18 +++++++++++++++++-
app/src/main/res/layout/fragment_tracking.xml | 19 +++++++++++++++++--
app/src/main/res/values-de/strings.xml | 5 +++++
app/src/main/res/values/strings.xml | 5 +++++
6 files changed, 55 insertions(+), 7 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 35c2cbd0..1ce73c05 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -10,6 +10,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
+import android.widget.TextView
import androidx.activity.addCallback
import androidx.cardview.widget.CardView
import androidx.databinding.DataBindingUtil
@@ -126,6 +127,11 @@ class TrackingFragment : Fragment() {
val observeTrackerButton: CardView = view.findViewById(R.id.tracking_observation)
val map: MapView = view.findViewById(R.id.map)
val includedLayout: View = view.findViewById(R.id.manufacturer_website)
+ val identifierExplanation: TextView = view.findViewById(R.id.identifier_explanation)
+
+ trackingViewModel.deviceType.observe(viewLifecycleOwner) { deviceType ->
+ identifierExplanation.text = Utility.getExplanationTextForDeviceType(deviceType)
+ }
includedLayout.setOnClickListener {
trackingViewModel.clickOnWebsite(requireContext())
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
index 7960432c..706a2a27 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
@@ -29,6 +29,8 @@ class TrackingViewModel @Inject constructor(
val manufacturerWebsiteUrl = MutableLiveData("https://www.apple.com/airtag/")
+ var deviceType = MutableLiveData(DeviceType.UNKNOWN)
+
val error = MutableLiveData(false)
val falseAlarm = MutableLiveData(false)
@@ -73,10 +75,9 @@ class TrackingViewModel @Inject constructor(
connectable.postValue(it.device is Connectable)
showNfcHint.postValue(it.deviceType == DeviceType.AIRTAG)
manufacturerWebsiteUrl.postValue(it.device.deviceContext.websiteManufacturer)
- val deviceType = it.deviceType
- if (deviceType != null) {
- this.canBeIgnored.postValue(deviceType.canBeIgnored())
- }
+ deviceType.postValue(it.device.deviceContext.deviceType)
+ Timber.d("Set Device type: ${it.device.deviceContext.deviceType}")
+ canBeIgnored.postValue(it.device.deviceContext.deviceType.canBeIgnored())
val notification = notificationRepository.notificationForDevice(it).firstOrNull()
notification?.let { notificationId.postValue(it.notificationId) }
} else {
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 84c767b1..51796b6a 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
@@ -4,7 +4,6 @@ import android.Manifest
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
-import android.graphics.BitmapFactory
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
@@ -15,6 +14,7 @@ import androidx.core.content.ContextCompat
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.device.ConnectionState
+import de.seemoo.at_tracking_detection.database.models.device.DeviceType
import de.seemoo.at_tracking_detection.database.models.Location as LocationModel
import de.seemoo.at_tracking_detection.ui.OnboardingActivity
import de.seemoo.at_tracking_detection.util.ble.DbmToPercent
@@ -214,6 +214,22 @@ object Utility {
}
}
+ fun getExplanationTextForDeviceType(deviceType: DeviceType?): String {
+ Timber.d("get Explanation for DeviceType: $deviceType")
+ return when (deviceType) {
+ DeviceType.APPLE -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.explanation_apple)
+ DeviceType.AIRPODS -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.explanation_apple)
+ DeviceType.FIND_MY -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.explanation_apple)
+ DeviceType.AIRTAG-> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.explanation_apple)
+ DeviceType.SAMSUNG -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.explanation_samsung)
+ DeviceType.GALAXY_SMART_TAG -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.explanation_samsung)
+ 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)
+ else -> ""
+ }
+ }
+
private fun rssiToQuality(percentage: Float): Int {
return when (percentage) {
in 0.75..1.0 -> {
diff --git a/app/src/main/res/layout/fragment_tracking.xml b/app/src/main/res/layout/fragment_tracking.xml
index 25bba1bb..a38e5117 100644
--- a/app/src/main/res/layout/fragment_tracking.xml
+++ b/app/src/main/res/layout/fragment_tracking.xml
@@ -216,11 +216,26 @@
android:autoSizeTextType="uniform"
android:text="@string/tracking_info"
android:visibility="@{vm.showNfcHint ? View.VISIBLE : View.GONE}"
- app:layout_constraintBottom_toTopOf="@id/additional_tiles"
+ app:layout_constraintBottom_toTopOf="@id/identifier_explanation"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tracking_tiles" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/identifier_explanation">
Entwickler: %s
Maintainer: %s
+ Achtung:\nApple Geräte ändern ihre Kennung alle 24 Stunden nachdem sie einige Zeit vom Besitzergerät getrennt sind.\nAirTags, AirPods, FindMy-Geräte und andere Apple Geräte werden jeden Tag als ein neues Gerät erkannt.
+ Achtung:\nSamsung Geräte ändern ihre Kennung alle 24 Stunden nachdem sie längere Zeit vom Besitzergerät getrennt sind.\nSmartTags und andere Samsung Geräte werden jeden Tag als ein neues Gerät erkannt.
+ Achtung:\nTile Geräte ändern ihre Kennung nicht.\nDies ermöglicht zwar eine genauere Tracking Erkennung, führt aber auch zu mehr falschen Warnungen.\nFalls sie über ein Tile gewarnt werden, überprüfen Sie ob der Tracker nicht von jemanden aus ihrer Umgebung (Freunde, Familie, Kollegen, Nachbarn) stammt.
+ Achtung:\nChipolo Geräte ändern ihre Kennung nicht.\nDies ermöglicht zwar eine genauere Tracking Erkennung, führt aber auch zu mehr falschen Warnungen.\nFalls sie über einen Chipolo-Tracker gewarnt werden, überprüfen Sie ob der Tracker nicht von jemanden aus ihrer Umgebung (Freunde, Familie, Kollegen, Nachbarn) stammt.
+
Mögliche Tracker
Sichere Tracker
Legende
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 62dff339..2cdc005b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -362,6 +362,11 @@
No internet connection
You are currently not connected to the internet. You will find articles that will help you navigate the app here when you are connected to the internet.
+ Please note:\nApple devices change their identifier every 24 hours once they are disconnected from their owner device for a certain time.\nAirTags, AirPods, FindMy and other Apple devices will be detected as a new device every day.
+ Please note:\nSamsung devices change their identifier every 24 hours once they are disconnected from their owner device for a longer time.\nSmartTags and other Samsung SmartThings devices will be detected as a new device every day.
+ Please note:\nTile devices do not change their identifier.\nWhile this allows for a better tracking detection, this can also lead to false warnings.\nIn case of a tracking 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.\nWhile this allows for a better tracking detection, this can also lead to false warnings.\nIn case of a tracking warning, please check if the device is not carried by someone around you (friends, family, colleges, neighbours etc.).
+
Copyright
Developer: %s
Maintainer: %s
From 07c434e44098cfee7414809e8301e15a42616c92 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 6 Mar 2024 16:09:44 +0100
Subject: [PATCH 094/154] add explanation for safe trackers in manual scan
---
.../ui/scan/ScanFragment.kt | 16 ++++++
app/src/main/res/layout/fragment_scan.xml | 52 +++++++++++++++----
app/src/main/res/values-de/strings.xml | 2 +
app/src/main/res/values/strings.xml | 2 +
4 files changed, 63 insertions(+), 9 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
index 32f69694..30767cc3 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
@@ -9,6 +9,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
+import android.widget.ImageButton
+import android.widget.LinearLayout
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -109,6 +111,12 @@ class ScanFragment : Fragment() {
}
}
+ val infoButton = view.findViewById(R.id.info_button)
+ infoButton.setOnClickListener {
+ toggleInfoLayoutVisibility(view)
+ }
+
+
// val sortBySignalStrength = view.findViewById(R.id.sort_option_signal_strength)
// val sortByDetectionOrder = view.findViewById(R.id.sort_option_order_detection)
// val sortByAddress = view.findViewById(R.id.sort_option_address)
@@ -133,6 +141,14 @@ class ScanFragment : Fragment() {
scanViewModel.bluetoothEnabled.postValue(BLEScanner.isBluetoothOn())
}
+ private fun toggleInfoLayoutVisibility(view: View) {
+ // Find the info layout by its ID
+ val infoLayout = view.findViewById(R.id.info_layout)
+
+ // Toggle the visibility
+ infoLayout.visibility = if (infoLayout.visibility == View.VISIBLE) View.GONE else View.VISIBLE
+ }
+
private val scanCallback: ScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult?) {
super.onScanResult(callbackType, result)
diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml
index 8e44537a..20e515c7 100644
--- a/app/src/main/res/layout/fragment_scan.xml
+++ b/app/src/main/res/layout/fragment_scan.xml
@@ -152,17 +152,51 @@
app:setAdapter="@{adapter_high_risk}" />
-
+
+
+
+
+
+
+
+
+
+ android:orientation="vertical"
+ android:visibility="gone"
+ android:padding="16dp">
+
+
+
+
Achtung:\nTile Geräte ändern ihre Kennung nicht.\nDies ermöglicht zwar eine genauere Tracking Erkennung, führt aber auch zu mehr falschen Warnungen.\nFalls sie über ein Tile gewarnt werden, überprüfen Sie ob der Tracker nicht von jemanden aus ihrer Umgebung (Freunde, Familie, Kollegen, Nachbarn) stammt.
Achtung:\nChipolo Geräte ändern ihre Kennung nicht.\nDies ermöglicht zwar eine genauere Tracking Erkennung, führt aber auch zu mehr falschen Warnungen.\nFalls sie über einen Chipolo-Tracker gewarnt werden, überprüfen Sie ob der Tracker nicht von jemanden aus ihrer Umgebung (Freunde, Familie, Kollegen, Nachbarn) stammt.
+ 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.
+
Mögliche Tracker
Sichere Tracker
Legende
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2cdc005b..4135a619 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -367,6 +367,8 @@
Please note:\nTile devices do not change their identifier.\nWhile this allows for a better tracking detection, this can also lead to false warnings.\nIn case of a tracking 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.\nWhile this allows for a better tracking detection, this can also lead to false warnings.\nIn case of a tracking warning, please check if the device is not carried by someone around you (friends, family, colleges, neighbours etc.).
+ 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.
+
Copyright
Developer: %s
Maintainer: %s
From 9f044b3e4e2620ff1aed4639511a8df0e9460169 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 9 Mar 2024 16:29:50 +0100
Subject: [PATCH 095/154] ignored trackers will be treated as safe trackers in
manual scan
---
.../detection/ScanBluetoothWorker.kt | 8 ++--
.../ui/scan/ScanViewModel.kt | 44 ++++++++++---------
app/src/main/res/values-de/strings.xml | 2 +-
app/src/main/res/values/strings.xml | 2 +-
4 files changed, 31 insertions(+), 25 deletions(-)
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 4b58fb9b..07b43684 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
@@ -244,13 +244,15 @@ class ScanBluetoothWorker @AssistedInject constructor(
longitude: Double?,
accuracy: Float?,
discoveryDate: LocalDateTime,
- ) {
- saveDevice(scanResult, discoveryDate) ?: return // return when device does not qualify to be saved
+ ): Pair {
+ val deviceSaved = saveDevice(scanResult, discoveryDate) ?: return Pair(null, null) // return when device does not qualify to be saved
// set locationId to null if gps location could not be retrieved
val locId: Int? = saveLocation(latitude, longitude, discoveryDate, accuracy)?.locationId
- saveBeacon(scanResult, discoveryDate, locId)
+ val beaconSaved = saveBeacon(scanResult, discoveryDate, locId) ?: return Pair(null, null)
+
+ return Pair(deviceSaved, beaconSaved)
}
private suspend fun saveBeacon(
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 44444c6f..fde8d0ea 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
@@ -1,14 +1,15 @@
package de.seemoo.at_tracking_detection.ui.scan
import android.bluetooth.le.ScanResult
-import android.widget.TextView
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.toArgb
+//import android.widget.TextView
+//import androidx.compose.ui.graphics.Color
+//import androidx.compose.ui.graphics.toArgb
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
+import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
import de.seemoo.at_tracking_detection.database.models.device.DeviceManager
@@ -19,7 +20,7 @@ import de.seemoo.at_tracking_detection.database.repository.ScanRepository
import de.seemoo.at_tracking_detection.detection.LocationProvider
import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker
import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker.Companion.TIME_BETWEEN_BEACONS
-import de.seemoo.at_tracking_detection.util.Utility
+//import de.seemoo.at_tracking_detection.util.Utility
import de.seemoo.at_tracking_detection.util.ble.BLEScanner
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.async
@@ -88,6 +89,9 @@ class ScanViewModel @Inject constructor(
}
}
+ val deviceRepository = ATTrackingDetectionApplication.getCurrentApp()?.deviceRepository ?: return
+ val device = deviceRepository.getDevice(uniqueIdentifier)
+
val bluetoothDeviceListHighRiskValue = bluetoothDeviceListHighRisk.value ?: return
val bluetoothDeviceListLowRiskValue = bluetoothDeviceListLowRisk.value ?: return
@@ -98,7 +102,7 @@ class ScanViewModel @Inject constructor(
getPublicKey(it) == uniqueIdentifier
}
- if (BaseDevice.getConnectionState(scanResult) in DeviceManager.unsafeConnectionState) {
+ if (BaseDevice.getConnectionState(scanResult) in DeviceManager.unsafeConnectionState && (device != null && !device.ignore) || device == null) {
// only add possible devices to list
bluetoothDeviceListHighRiskValue.add(scanResult)
} else {
@@ -133,21 +137,21 @@ class ScanViewModel @Inject constructor(
}
}
- fun changeColorOf(sortOptions: List, sortOption: TextView) {
- val theme = Utility.getSelectedTheme()
- var color = Color.Gray
- if (theme){
- color = Color.LightGray
- }
-
- sortOptions.forEach {
- if(it == sortOption) {
- it.setBackgroundColor(color.toArgb())
- } else {
- it.setBackgroundColor(Color.Transparent.toArgb())
- }
- }
- }
+// fun changeColorOf(sortOptions: List, sortOption: TextView) {
+// val theme = Utility.getSelectedTheme()
+// var color = Color.Gray
+// if (theme){
+// color = Color.LightGray
+// }
+//
+// sortOptions.forEach {
+// if(it == sortOption) {
+// it.setBackgroundColor(color.toArgb())
+// } else {
+// it.setBackgroundColor(Color.Transparent.toArgb())
+// }
+// }
+// }
val isListEmpty: LiveData = MediatorLiveData().apply {
// Function to update the isListEmpty LiveData
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 92edcfd4..8e7b00b8 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -336,7 +336,7 @@
Achtung:\nTile Geräte ändern ihre Kennung nicht.\nDies ermöglicht zwar eine genauere Tracking Erkennung, führt aber auch zu mehr falschen Warnungen.\nFalls sie über ein Tile gewarnt werden, überprüfen Sie ob der Tracker nicht von jemanden aus ihrer Umgebung (Freunde, Familie, Kollegen, Nachbarn) stammt.
Achtung:\nChipolo Geräte ändern ihre Kennung nicht.\nDies ermöglicht zwar eine genauere Tracking Erkennung, führt aber auch zu mehr falschen Warnungen.\nFalls sie über einen Chipolo-Tracker gewarnt werden, überprüfen Sie ob der Tracker nicht von jemanden aus ihrer Umgebung (Freunde, Familie, Kollegen, Nachbarn) stammt.
- 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.
+ 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.
Mögliche Tracker
Sichere Tracker
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4135a619..51f8fa94 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -367,7 +367,7 @@
Please note:\nTile devices do not change their identifier.\nWhile this allows for a better tracking detection, this can also lead to false warnings.\nIn case of a tracking 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.\nWhile this allows for a better tracking detection, this can also lead to false warnings.\nIn case of a tracking warning, please check if the device is not carried by someone around you (friends, family, colleges, neighbours etc.).
- 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.
+ 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.
Copyright
Developer: %s
From 480711355df641c1a68eaffc4105b692dfb5fec4 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 11 Mar 2024 15:43:46 +0100
Subject: [PATCH 096/154] add Advanced Mode, that hides complicated to
understand options and menus
---
.../at_tracking_detection/ui/MainActivity.kt | 23 ++++++++++++++-
.../ui/dashboard/RiskDetailFragment.kt | 7 +++++
.../ui/settings/SettingsFragment.kt | 28 +++++++++++++++++++
.../at_tracking_detection/util/SharedPrefs.kt | 8 ++++++
.../ic_baseline_admin_panel_settings_24.xml | 7 +++++
.../main/res/layout/fragment_risk_detail.xml | 1 +
app/src/main/res/values-de/strings.xml | 5 +++-
app/src/main/res/values/strings.xml | 2 ++
app/src/main/res/xml/fragment_settings.xml | 6 ++++
9 files changed, 85 insertions(+), 2 deletions(-)
create mode 100644 app/src/main/res/drawable/ic_baseline_admin_panel_settings_24.xml
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt
index d9a08f70..c656ae8e 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt
@@ -25,13 +25,15 @@ import java.time.ZoneOffset
import javax.inject.Inject
@AndroidEntryPoint
-class MainActivity : AppCompatActivity() {
+class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
@Inject
lateinit var sharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ sharedPreferences.registerOnSharedPreferenceChangeListener(this)
+
window.navigationBarColor = SurfaceColors.SURFACE_2.getColor(this)
val configuration = Configuration.getInstance()
configuration.load(this, PreferenceManager.getDefaultSharedPreferences(this))
@@ -64,6 +66,13 @@ class MainActivity : AppCompatActivity() {
if (BuildConfig.DEBUG) {
appBarItems.plus(R.id.navigation_debug)
}
+
+ if (!SharedPrefs.advancedMode) {
+ val menu = navView.menu
+ val item = menu.findItem(R.id.navigation_allDevicesFragment)
+ item.isVisible = false
+ }
+
val appBarConfiguration = AppBarConfiguration(appBarItems)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
@@ -97,6 +106,7 @@ class MainActivity : AppCompatActivity() {
override fun onDestroy() {
+ sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
SharedPrefs.lastTimeOpened = dateTime
super.onDestroy()
}
@@ -109,4 +119,15 @@ class MainActivity : AppCompatActivity() {
companion object {
private val dateTime = LocalDateTime.now(ZoneOffset.UTC)
}
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ // Check if the changed preference is the advancedMode
+ if (key == "advanced_mode") {
+ // Update the visibility of the All Devices fragment menu item
+ val navView: BottomNavigationView = findViewById(R.id.main_nav_view)
+ val menu = navView.menu
+ val item = menu.findItem(R.id.navigation_allDevicesFragment)
+ item.isVisible = sharedPreferences?.getBoolean(key, false) ?: false
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailFragment.kt
index 7d2cfef2..616097ea 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailFragment.kt
@@ -13,6 +13,7 @@ import com.google.android.material.card.MaterialCardView
import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.databinding.FragmentRiskDetailBinding
+import de.seemoo.at_tracking_detection.util.SharedPrefs
import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
import javax.inject.Inject
@@ -77,6 +78,12 @@ class RiskDetailFragment : Fragment() {
findNavController().navigate(directions)
}
+ if (!SharedPrefs.advancedMode) {
+ view.findViewById(R.id.card_devices_found).visibility = View.GONE
+ } else {
+ view.findViewById(R.id.card_devices_found).visibility = View.VISIBLE
+ }
+
// view.findViewById(R.id.card_beacons_found).setOnClickListener {
// val directions =
// RiskDetailFragmentDirections.actionRiskDetailFragmentToDeviceMapFragment()
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/SettingsFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/SettingsFragment.kt
index eafca39c..9eb35fc3 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/SettingsFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/SettingsFragment.kt
@@ -11,6 +11,7 @@ import androidx.core.content.ContextCompat
import androidx.navigation.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SwitchPreferenceCompat
import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
@@ -38,6 +39,18 @@ class SettingsFragment : PreferenceFragmentCompat() {
findPreference("delete_study_data")?.isVisible = false
}
+ if (SharedPrefs.advancedMode) {
+ findPreference("use_location")?.isVisible = true
+ findPreference("use_low_power_ble")?.isVisible = true
+ findPreference("notification_priority_high")?.isVisible = true
+ findPreference("show_onboarding")?.isVisible = true
+ } else {
+ findPreference("use_location")?.isVisible = false
+ findPreference("use_low_power_ble")?.isVisible = false
+ findPreference("notification_priority_high")?.isVisible = false
+ findPreference("show_onboarding")?.isVisible = false
+ }
+
findPreference("information_contact")?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
view?.findNavController()?.navigate(R.id.action_settings_to_information)
@@ -73,6 +86,21 @@ class SettingsFragment : PreferenceFragmentCompat() {
private val sharedPreferenceListener =
SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, preferenceKey ->
when (preferenceKey) {
+ "advanced_mode" -> {
+ if (SharedPrefs.advancedMode) {
+ Timber.d("Enabled advanced mode!")
+ findPreference("use_location")?.isVisible = true
+ findPreference("use_low_power_ble")?.isVisible = true
+ findPreference("notification_priority_high")?.isVisible = true
+ findPreference("show_onboarding")?.isVisible = true
+ } else {
+ Timber.d("Disabled advanced mode!")
+ findPreference("use_location")?.isVisible = false
+ findPreference("use_low_power_ble")?.isVisible = false
+ findPreference("notification_priority_high")?.isVisible = false
+ findPreference("show_onboarding")?.isVisible = false
+ }
+ }
"share_data" -> {
if (SharedPrefs.shareData) {
Timber.d("Enabled background statistics sharing!")
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 8bfc79bf..d4a286e4 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
@@ -52,6 +52,14 @@ object SharedPrefs {
sharedPreferences.edit().putBoolean("share_data", value).apply()
}
+ var advancedMode: Boolean
+ get() {
+ return sharedPreferences.getBoolean("advanced_mode", false)
+ }
+ set(value) {
+ sharedPreferences.edit().putBoolean("advanced_mode", value).apply()
+ }
+
var token: String?
get() {
return sharedPreferences.getString("token", null)
diff --git a/app/src/main/res/drawable/ic_baseline_admin_panel_settings_24.xml b/app/src/main/res/drawable/ic_baseline_admin_panel_settings_24.xml
new file mode 100644
index 00000000..8a2ae054
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_admin_panel_settings_24.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_risk_detail.xml b/app/src/main/res/layout/fragment_risk_detail.xml
index 8ebdd3c6..dd0141b9 100644
--- a/app/src/main/res/layout/fragment_risk_detail.xml
+++ b/app/src/main/res/layout/fragment_risk_detail.xml
@@ -43,6 +43,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintWidth_percent="0.48"
android:layout_marginTop="22dp"
+ android:visibility="visible"
app:cardName="@{@string/other_devices_found}"
app:cardBackgroundColorInt="@{viewModel.riskColor}"
app:cardNumber="@{viewModel.totalNumberOfDevicesFound}"
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 8e7b00b8..569fb065 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -213,7 +213,7 @@
Gerätetypen
GERÄT IGNORIEREN
FEHLALARM
- Last scans
+ Letzte Scans
Testest du die App? Solange AirTags mit einem iPhone/iPad verbunden sind können sie nur gefunden werden, sofern es in den Einstellungen erlaubt wurde. Standardmäßig gilt: Trenne die Bluetooth Verbindung und warte 15min, dann werden sie gefunden. Verbundene Tracker werden nicht gespeichert.
Keiner
Die App scannt bereits im Hintergrund. Bitte warte 10s und starte dann den Scan erneut.
@@ -322,6 +322,9 @@
SmartTag (mit UWB)
+ Fügt erweiterte Funktionen zu AirGuard hinzu. Diese Optionen sind für fortgeschrittene Nutzer gedacht.
+ Experten Modus
+
https://tpe.seemoo.tu-darmstadt.de/articles/airguard_articles_de.json
Lesedauer: %d min
Keine Internetverbindung
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 51f8fa94..b544b51e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -28,6 +28,8 @@
Devices Currently Monitored
Support the project by sharing some anonymous data
Participate in study
+ Adds more options to AirGuard. These options are meant for advanced users.
+ Expert Mode
Discovered:
Beacons
Swipe to remove
diff --git a/app/src/main/res/xml/fragment_settings.xml b/app/src/main/res/xml/fragment_settings.xml
index 7d4eea26..986fba24 100644
--- a/app/src/main/res/xml/fragment_settings.xml
+++ b/app/src/main/res/xml/fragment_settings.xml
@@ -4,6 +4,12 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context=".ui.settings.SettingsFragment">
+
+
Date: Fri, 15 Mar 2024 15:07:09 +0100
Subject: [PATCH 097/154] when deleting old devices and beacons, locations that
are not used anymore also get deleted
---
.../database/daos/LocationDao.kt | 10 +++++++++-
.../database/repository/LocationRepository.kt | 12 ++++++++++++
.../detection/TrackingDetectorWorker.kt | 14 ++++++++++++++
3 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/LocationDao.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/LocationDao.kt
index 608adb3f..a23da719 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/LocationDao.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/LocationDao.kt
@@ -1,7 +1,6 @@
package de.seemoo.at_tracking_detection.database.daos
import androidx.room.*
-import de.seemoo.at_tracking_detection.database.models.Beacon
import de.seemoo.at_tracking_detection.database.models.Location as LocationModel
import kotlinx.coroutines.flow.Flow
import java.time.LocalDateTime
@@ -29,9 +28,18 @@ interface LocationDao {
@Query("SELECT COUNT(*) FROM location, beacon WHERE location.locationId = :locationId AND location.locationId = beacon.locationId")
fun getNumberOfBeaconsForLocation(locationId: Int): Int
+ @Query("SELECT * FROM location WHERE locationId NOT IN (SELECT DISTINCT locationId FROM beacon)")
+ fun getLocationsWithNoBeacons(): List
+
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(location: LocationModel): Long
@Update
suspend fun update(location: LocationModel)
+
+ @Delete
+ suspend fun delete(location: LocationModel)
+
+ @Delete
+ suspend fun deleteLocations(locations: List)
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/LocationRepository.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/LocationRepository.kt
index dc2fea6c..ed123420 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/LocationRepository.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/LocationRepository.kt
@@ -24,6 +24,8 @@ class LocationRepository @Inject constructor(
fun getNumberOfBeaconsForLocation(locationId: Int): Int = locationDao.getNumberOfBeaconsForLocation(locationId)
+ fun getLocationsWithNoBeacons(): List = locationDao.getLocationsWithNoBeacons()
+
@WorkerThread
suspend fun insert(location: LocationModel) {
locationDao.insert(location)
@@ -34,4 +36,14 @@ class LocationRepository @Inject constructor(
locationDao.update(location)
}
+ @WorkerThread
+ suspend fun delete(location: LocationModel) {
+ locationDao.delete(location)
+ }
+
+ @WorkerThread
+ suspend fun deleteLocations(locations: List) {
+ locationDao.deleteLocations(locations)
+ }
+
}
\ No newline at end of file
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 39d69fec..ca9c1268 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
@@ -9,6 +9,7 @@ import androidx.work.Data
import androidx.work.WorkerParameters
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
+import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
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.models.Beacon
@@ -129,7 +130,10 @@ class TrackingDetectorWorker @AssistedInject constructor(
Timber.d("Deleting ${beaconsToBeDeleted.size} beacons")
beaconRepository.deleteBeacons(beaconsToBeDeleted)
Timber.d("Deleting Beacons successful")
+ } else {
+ Timber.d("No old beacons to delete")
}
+
val devicesToBeDeleted = deviceRepository.getDevicesOlderThanWithoutNotifications(deleteSafeTrackersBefore)
if (devicesToBeDeleted.isNotEmpty()) {
Timber.d("Deleting ${devicesToBeDeleted.size} devices")
@@ -139,6 +143,16 @@ class TrackingDetectorWorker @AssistedInject constructor(
if (beaconsToBeDeleted.isEmpty() && devicesToBeDeleted.isEmpty()) {
Timber.d("No old devices or beacons to delete")
}
+
+ val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return
+ val locationsToBeDeleted = locationRepository.getLocationsWithNoBeacons()
+ if (locationsToBeDeleted.isNotEmpty()) {
+ Timber.d("Deleting ${locationsToBeDeleted.size} locations")
+ locationRepository.deleteLocations(locationsToBeDeleted)
+ Timber.d("Deleting Locations successful")
+ } else {
+ Timber.d("No locations to delete")
+ }
}
companion object {
From ae0532d94bc4bc37fc0d6c61ef32a16cb876c9d3 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Fri, 15 Mar 2024 16:06:53 +0100
Subject: [PATCH 098/154] add Clustering to the map
---
app/build.gradle | 1 +
.../at_tracking_detection/util/Utility.kt | 33 +++++++++++++------
2 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 9c54d6e2..61a00197 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -123,6 +123,7 @@ dependencies {
implementation 'com.github.AppIntro:AppIntro:6.1.0'
implementation 'org.osmdroid:osmdroid-android:6.1.16'
+ implementation 'com.github.MKergall:osmbonuspack:6.9.0'
implementation 'com.github.ybq:Android-SpinKit:1.4.0'
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 51796b6a..76ad04d6 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
@@ -20,6 +20,8 @@ import de.seemoo.at_tracking_detection.ui.OnboardingActivity
import de.seemoo.at_tracking_detection.util.ble.DbmToPercent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
+import org.osmdroid.bonuspack.clustering.RadiusMarkerClusterer
+import org.osmdroid.bonuspack.utils.BonusPackHelper
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.util.BoundingBox
import org.osmdroid.util.GeoPoint
@@ -102,7 +104,6 @@ object Utility {
val mapController = map.controller
val geoPointList = ArrayList()
- val markerList = ArrayList()
map.setTileSource(TileSourceFactory.MAPNIK)
map.setUseDataConnection(true)
@@ -110,6 +111,16 @@ object Utility {
map.overlays.add(copyrightOverlay)
+ val iconDrawable = R.drawable.ic_baseline_location_on_45_black
+
+ val clusterer = RadiusMarkerClusterer(context)
+ val clusterIcon = BonusPackHelper.getBitmapFromVectorDrawable(context, iconDrawable)
+ clusterer.setIcon(clusterIcon)
+ clusterer.setRadius(100)
+ clusterer.mAnchorU = Marker.ANCHOR_CENTER
+ clusterer.mAnchorV = Marker.ANCHOR_BOTTOM
+ clusterer.mTextAnchorV = 0.6f
+
withContext(Dispatchers.Default) {
locationList
.filter { it.locationId != 0 }
@@ -123,28 +134,30 @@ object Utility {
marker.position = geoPoint
marker.icon = ContextCompat.getDrawable(
context,
- R.drawable.ic_baseline_location_on_45_black
+ iconDrawable
)
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
geoPointList.add(geoPoint)
- markerList.add(marker)
marker.setOnMarkerClickListener { clickedMarker, _ ->
clickedMarker.closeInfoWindow()
false
}
+
+ clusterer.add(marker)
}
}
- map.overlays.addAll(markerList)
+ map.overlays.add(clusterer)
Timber.d("Added ${geoPointList.size} markers to the map!")
- if (connectWithPolyline) {
- val line = Polyline(map)
- line.setPoints(geoPointList)
- line.infoWindow = null
- map.overlays.add(line)
- }
+ // TODO: temporarily disabled
+// if (connectWithPolyline) {
+// val line = Polyline(map)
+// line.setPoints(geoPointList)
+// line.infoWindow = null
+// map.overlays.add(line)
+// }
if (geoPointList.isEmpty()) {
mapController.setZoom(MAX_ZOOM_LEVEL)
From 7b61934f0f6a92427de3038ef4d0db4d22e22018 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 17 Mar 2024 12:48:16 +0100
Subject: [PATCH 099/154] highly optimize speed in loading map markers, update
some dependencies
---
app/build.gradle | 16 +--
.../ui/dashboard/DeviceMapFragment.kt | 49 ++------
.../ui/tracking/TrackingFragment.kt | 114 +++++++++---------
.../at_tracking_detection/util/Utility.kt | 30 +++--
build.gradle | 4 +-
5 files changed, 94 insertions(+), 119 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 61a00197..5ff2301e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -109,12 +109,12 @@ 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.5'
+ implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12'
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.12.0'
- debugImplementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.5'
+ debugImplementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12'
implementation "com.google.dagger:hilt-android:$hilt_version"
implementation 'androidx.hilt:hilt-work:1.2.0'
@@ -122,7 +122,7 @@ dependencies {
implementation 'com.github.AppIntro:AppIntro:6.1.0'
- implementation 'org.osmdroid:osmdroid-android:6.1.16'
+ implementation 'org.osmdroid:osmdroid-android:6.1.18'
implementation 'com.github.MKergall:osmbonuspack:6.9.0'
implementation 'com.github.ybq:Android-SpinKit:1.4.0'
@@ -144,7 +144,7 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation "androidx.room:room-testing:$room_version"
- androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
+ 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'
@@ -160,15 +160,15 @@ dependencies {
// Integration with activities
implementation 'androidx.activity:activity-compose:1.8.2'
// Compose Material Design
- implementation 'androidx.compose.material:material:1.6.2'
+ implementation 'androidx.compose.material:material:1.6.3'
// Animations
- implementation 'androidx.compose.animation:animation:1.6.2'
+ implementation 'androidx.compose.animation:animation:1.6.3'
// Tooling support (Previews, etc.)
- implementation 'androidx.compose.ui:ui-tooling:1.6.2'
+ implementation 'androidx.compose.ui:ui-tooling:1.6.3'
// Integration with ViewModels
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
// UI Tests
- androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.2'
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.3'
// When using a MDC theme
implementation "com.google.android.material:compose-theme-adapter:1.2.1"
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
index 7c4e7226..09b40051 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
@@ -12,9 +12,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
-import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
-import de.seemoo.at_tracking_detection.database.models.Location
import de.seemoo.at_tracking_detection.databinding.FragmentDeviceMapBinding
import de.seemoo.at_tracking_detection.util.Utility
import kotlinx.coroutines.launch
@@ -58,45 +56,18 @@ class DeviceMapFragment : Fragment() {
Utility.enableMyLocationOverlay(map)
val deviceAddress = this.deviceAddress
- if (!deviceAddress.isNullOrEmpty()) {
- viewModel.markerLocations.observe(viewLifecycleOwner) {
- lifecycleScope.launch {
- val locationList = arrayListOf()
- val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return@launch
+ val locationLiveData = if (!deviceAddress.isNullOrEmpty()) viewModel.markerLocations else viewModel.allLocations
- it.filter { it.locationId != null && it.locationId != 0 }
- .map {
- val location = locationRepository.getLocationWithId(it.locationId!!)
- if (location != null) {
- locationList.add(location)
- }
- }
-
- Utility.setGeoPointsFromListOfLocations(locationList.toList(), map, true)
- }.invokeOnCompletion {
- viewModel.isMapLoading.postValue(false)
- }
- }
- } else {
- viewModel.allLocations.observe(viewLifecycleOwner) {
- lifecycleScope.launch {
- val locationList = arrayListOf()
- val locationRepository =
- ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return@launch
-
- it.filter { it.locationId != null && it.locationId != 0 }
- .map {
- val location = locationRepository.getLocationWithId(it.locationId!!)
- if (location != null) {
- locationList.add(location)
- }
- }
-
- Utility.setGeoPointsFromListOfLocations(locationList.toList(), map)
- }.invokeOnCompletion {
- viewModel.isMapLoading.postValue(false)
- }
+ locationLiveData.observe(viewLifecycleOwner) { locations ->
+ lifecycleScope.launch {
+ val locationList = Utility.fetchLocations(locations)
+ Utility.setGeoPointsFromListOfLocations(locationList, map)
+ }.invokeOnCompletion {
+ viewModel.isMapLoading.postValue(false)
}
}
}
+
+
+
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 1ce73c05..76847dce 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -25,7 +25,6 @@ import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
-import de.seemoo.at_tracking_detection.database.models.Location
import de.seemoo.at_tracking_detection.database.models.device.Connectable
import de.seemoo.at_tracking_detection.database.models.device.DeviceManager
import de.seemoo.at_tracking_detection.databinding.FragmentTrackingBinding
@@ -121,85 +120,49 @@ class TrackingFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- val feedbackButton: CardView = view.findViewById(R.id.tracking_feedback)
- val playSoundCard: CardView = view.findViewById(R.id.tracking_play_sound)
- val trackingDetailButton: CardView = view.findViewById(R.id.tracking_detail_scan)
- val observeTrackerButton: CardView = view.findViewById(R.id.tracking_observation)
- val map: MapView = view.findViewById(R.id.map)
- val includedLayout: View = view.findViewById(R.id.manufacturer_website)
- val identifierExplanation: TextView = view.findViewById(R.id.identifier_explanation)
-
trackingViewModel.deviceType.observe(viewLifecycleOwner) { deviceType ->
- identifierExplanation.text = Utility.getExplanationTextForDeviceType(deviceType)
+ view.findViewById(R.id.identifier_explanation).text =
+ Utility.getExplanationTextForDeviceType(deviceType)
}
- includedLayout.setOnClickListener {
+ view.findViewById(R.id.manufacturer_website).setOnClickListener {
trackingViewModel.clickOnWebsite(requireContext())
}
- feedbackButton.setOnClickListener {
- val directions: NavDirections =
- TrackingFragmentDirections.actionTrackingFragmentToFeedbackFragment(notificationId)
- findNavController().navigate(directions)
- }
-
- trackingDetailButton.setOnClickListener {
- val deviceAddress: String = trackingViewModel.deviceAddress.value ?: return@setOnClickListener
- val directions: NavDirections =
- TrackingFragmentDirections.actionTrackingToScanDistance(deviceAddress)
- findNavController().navigate(directions)
+ view.findViewById(R.id.tracking_feedback).setOnClickListener {
+ navigateToFeedbackFragment()
}
- observeTrackerButton.setOnClickListener {
- val deviceAddress: String = trackingViewModel.deviceAddress.value ?: return@setOnClickListener
- val directions: NavDirections =
- TrackingFragmentDirections.actionTrackingToObserveTracker(deviceAddress)
- findNavController().navigate(directions)
+ view.findViewById(R.id.tracking_detail_scan).setOnClickListener {
+ trackingViewModel.deviceAddress.value?.let { deviceAddress ->
+ navigateToScanDistance(deviceAddress)
+ }
}
- playSoundCard.setOnClickListener {
- if (!Utility.checkAndRequestPermission(android.Manifest.permission.BLUETOOTH_CONNECT)) {
- return@setOnClickListener
+ view.findViewById(R.id.tracking_observation).setOnClickListener {
+ trackingViewModel.deviceAddress.value?.let { deviceAddress ->
+ navigateToObserveTracker(deviceAddress)
}
+ }
- val baseDevice = trackingViewModel.device.value
- if (baseDevice != null && baseDevice.device is Connectable) {
- toggleSound()
- } else {
- Snackbar.make(
- view,
- getString(R.string.tracking_device_not_connectable),
- Snackbar.LENGTH_LONG
- ).show()
- }
+ view.findViewById(R.id.tracking_play_sound).setOnClickListener {
+ handlePlaySound()
}
+ val map: MapView = view.findViewById(R.id.map)
Utility.enableMyLocationOverlay(map)
- trackingViewModel.markerLocations.observe(viewLifecycleOwner) {
+ trackingViewModel.markerLocations.observe(viewLifecycleOwner) { beacons ->
lifecycleScope.launch {
trackingViewModel.isMapLoading.postValue(true)
-
- val locationList = arrayListOf()
- val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository!!
-
- it.filter { it.locationId != null && it.locationId != 0 }
- .map {
- val location = locationRepository.getLocationWithId(it.locationId!!)
- if (location != null) {
- locationList.add(location)
- }
- }
-
- // This is the per Device View
- Utility.setGeoPointsFromListOfLocations(locationList.toList(), map, true)
- }.invokeOnCompletion {
+ val locationList = Utility.fetchLocations(beacons)
+ Utility.setGeoPointsFromListOfLocations(locationList, map)
trackingViewModel.isMapLoading.postValue(false)
}
}
- trackingViewModel.soundPlaying.observe(viewLifecycleOwner) {
- if (!it) {
+ trackingViewModel.soundPlaying.observe(viewLifecycleOwner) { isPlaying ->
+ if (!isPlaying) {
try {
requireContext().unbindService(serviceConnection)
} catch (e: IllegalArgumentException) {
@@ -213,6 +176,41 @@ class TrackingFragment : Fragment() {
addInteractions(view)
}
+ private fun navigateToFeedbackFragment() {
+ val directions: NavDirections =
+ TrackingFragmentDirections.actionTrackingFragmentToFeedbackFragment(notificationId)
+ findNavController().navigate(directions)
+ }
+
+ private fun navigateToScanDistance(deviceAddress: String) {
+ val directions: NavDirections =
+ TrackingFragmentDirections.actionTrackingToScanDistance(deviceAddress)
+ findNavController().navigate(directions)
+ }
+
+ private fun navigateToObserveTracker(deviceAddress: String) {
+ val directions: NavDirections =
+ TrackingFragmentDirections.actionTrackingToObserveTracker(deviceAddress)
+ findNavController().navigate(directions)
+ }
+
+ private fun handlePlaySound() {
+ if (!Utility.checkAndRequestPermission(android.Manifest.permission.BLUETOOTH_CONNECT)) {
+ return
+ }
+
+ val baseDevice = trackingViewModel.device.value
+ if (baseDevice != null && baseDevice.device is Connectable) {
+ toggleSound()
+ } else {
+ Snackbar.make(
+ requireView(),
+ getString(R.string.tracking_device_not_connectable),
+ Snackbar.LENGTH_LONG
+ ).show()
+ }
+ }
+
private fun addInteractions(view: View) {
val button = view.findViewById(R.id.open_map_button)
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 76ad04d6..02042805 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
@@ -13,9 +13,10 @@ import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale
import androidx.core.content.ContextCompat
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
+import de.seemoo.at_tracking_detection.database.models.Beacon
+import de.seemoo.at_tracking_detection.database.models.Location
import de.seemoo.at_tracking_detection.database.models.device.ConnectionState
import de.seemoo.at_tracking_detection.database.models.device.DeviceType
-import de.seemoo.at_tracking_detection.database.models.Location as LocationModel
import de.seemoo.at_tracking_detection.ui.OnboardingActivity
import de.seemoo.at_tracking_detection.util.ble.DbmToPercent
import kotlinx.coroutines.Dispatchers
@@ -28,7 +29,6 @@ import org.osmdroid.util.GeoPoint
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.CopyrightOverlay
import org.osmdroid.views.overlay.Marker
-import org.osmdroid.views.overlay.Polyline
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
import timber.log.Timber
@@ -95,9 +95,8 @@ object Utility {
}
suspend fun setGeoPointsFromListOfLocations(
- locationList: List,
+ locationList: List,
map: MapView,
- connectWithPolyline: Boolean = false,
): Boolean {
val context = ATTrackingDetectionApplication.getAppContext()
val copyrightOverlay = CopyrightOverlay(context)
@@ -151,14 +150,6 @@ object Utility {
map.overlays.add(clusterer)
Timber.d("Added ${geoPointList.size} markers to the map!")
- // TODO: temporarily disabled
-// if (connectWithPolyline) {
-// val line = Polyline(map)
-// line.setPoints(geoPointList)
-// line.infoWindow = null
-// map.overlays.add(line)
-// }
-
if (geoPointList.isEmpty()) {
mapController.setZoom(MAX_ZOOM_LEVEL)
return false
@@ -183,6 +174,21 @@ object Utility {
return true
}
+ fun fetchLocations(locations: List): List {
+ val uniqueLocations = locations
+ .distinctBy { it.locationId } // Filter out duplicates based on locationId
+ .filter { it.locationId != null && it.locationId != 0 } // Filter out invalid locationId entries
+
+ val locationList = arrayListOf()
+ val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return emptyList()
+
+ uniqueLocations.mapNotNullTo(locationList) {
+ locationRepository.getLocationWithId(it.locationId!!)
+ }
+
+ return locationList
+ }
+
fun setSelectedTheme(sharedPreferences: SharedPreferences) {
when (sharedPreferences.getString("app_theme", "system_default")) {
diff --git a/build.gradle b/build.gradle
index d8d10c09..14f36fa3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,10 +1,10 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.9.22'
- ext.hilt_version = '2.48'
+ ext.hilt_version = '2.51'
ext.room_version = '2.6.1'
ext.compose_version = '1.1.1'
- ext.about_libraries_version = '10.8.3'
+ ext.about_libraries_version = '11.1.0'
ext.work_version = '2.8.1'
repositories {
google()
From 7e67e4f265780acc14016f487fafb344c86f0b76 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 17 Mar 2024 13:06:36 +0100
Subject: [PATCH 100/154] fix bug where numbers of locations being displayed
were inconsistent
---
.../ui/dashboard/DeviceMapFragment.kt | 23 ++++++++++++++-----
.../ui/dashboard/RiskDetailViewModel.kt | 10 ++------
.../ui/tracking/TrackingFragment.kt | 2 +-
.../at_tracking_detection/util/Utility.kt | 2 +-
4 files changed, 21 insertions(+), 16 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
index 09b40051..f531fa74 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
@@ -9,12 +9,17 @@ import androidx.core.view.ViewCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
+import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
+import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
+import de.seemoo.at_tracking_detection.database.models.Beacon
+import de.seemoo.at_tracking_detection.database.models.Location
import de.seemoo.at_tracking_detection.databinding.FragmentDeviceMapBinding
import de.seemoo.at_tracking_detection.util.Utility
+import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
import kotlinx.coroutines.launch
import org.osmdroid.views.MapView
@@ -55,19 +60,25 @@ class DeviceMapFragment : Fragment() {
viewModel.isMapLoading.postValue(true)
Utility.enableMyLocationOverlay(map)
- val deviceAddress = this.deviceAddress
- val locationLiveData = if (!deviceAddress.isNullOrEmpty()) viewModel.markerLocations else viewModel.allLocations
+ val locationLiveData: LiveData> = if (deviceAddress.isNullOrEmpty()) {
+ viewModel.allLocations
+ } else {
+ viewModel.markerLocations
+ }
locationLiveData.observe(viewLifecycleOwner) { locations ->
lifecycleScope.launch {
- val locationList = Utility.fetchLocations(locations)
+ val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return@launch
+ val locationList: List = if (deviceAddress.isNullOrEmpty()) {
+ locationRepository.locationsSince(RiskLevelEvaluator.relevantTrackingDateForRiskCalculation)
+ } else {
+ Utility.fetchLocationListFromBeaconList(locations)
+ }
+
Utility.setGeoPointsFromListOfLocations(locationList, map)
}.invokeOnCompletion {
viewModel.isMapLoading.postValue(false)
}
}
}
-
-
-
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailViewModel.kt
index 091e2e47..f6c8fc91 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskDetailViewModel.kt
@@ -10,13 +10,11 @@ import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
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.models.Beacon
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.database.repository.LocationRepository
import de.seemoo.at_tracking_detection.database.repository.ScanRepository
import de.seemoo.at_tracking_detection.util.risk.RiskLevel
import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
-import kotlinx.coroutines.flow.Flow
import timber.log.Timber
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
@@ -28,7 +26,7 @@ class RiskDetailViewModel @Inject constructor(
deviceRepository: DeviceRepository,
scanRepository: ScanRepository,
val beaconRepository: BeaconRepository,
- val locationRepository: LocationRepository,
+ private val locationRepository: LocationRepository,
) : ViewModel() {
private val relevantDate = RiskLevelEvaluator.relevantTrackingDateForRiskCalculation
@@ -40,7 +38,7 @@ class RiskDetailViewModel @Inject constructor(
var riskColor: Int
val numberOfTrackersFound = deviceRepository.trackingDevicesNotIgnoredSinceCount(RiskLevelEvaluator.relevantTrackingDateForRiskCalculation).asLiveData()
- val totalLocationsTrackedCount= locationRepository.locationsSinceCount(relevantDate).asLiveData()
+ val totalLocationsTrackedCount = locationRepository.locationsSinceCount(relevantDate).asLiveData()
// val discoveredBeacons: List = beaconRepository.getBeaconsForDevices(trackersFound)
@@ -58,10 +56,6 @@ class RiskDetailViewModel @Inject constructor(
scanDates.joinToString(separator = "\n")
}
- fun allBeacons(): Flow> {
- return beaconRepository.getBeaconsSince(relevantDate)
- }
-
init {
val context = ATTrackingDetectionApplication.getAppContext()
riskColor = when (riskLevelEvaluator.evaluateRiskLevel()) {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 76847dce..2d49926c 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -155,7 +155,7 @@ class TrackingFragment : Fragment() {
trackingViewModel.markerLocations.observe(viewLifecycleOwner) { beacons ->
lifecycleScope.launch {
trackingViewModel.isMapLoading.postValue(true)
- val locationList = Utility.fetchLocations(beacons)
+ val locationList = Utility.fetchLocationListFromBeaconList(beacons)
Utility.setGeoPointsFromListOfLocations(locationList, map)
trackingViewModel.isMapLoading.postValue(false)
}
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 02042805..bcc2287a 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
@@ -174,7 +174,7 @@ object Utility {
return true
}
- fun fetchLocations(locations: List): List {
+ fun fetchLocationListFromBeaconList(locations: List): List {
val uniqueLocations = locations
.distinctBy { it.locationId } // Filter out duplicates based on locationId
.filter { it.locationId != null && it.locationId != 0 } // Filter out invalid locationId entries
From e0ab32f8bac713e4aee114542c8d1248b1875011 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 17 Mar 2024 13:47:11 +0100
Subject: [PATCH 101/154] fix bug where map markers where not displayed
correctly in TrackingFragment
---
.../ui/tracking/TrackingFragment.kt | 40 +++++++++++++++++--
1 file changed, 37 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 2d49926c..3cb8d4e9 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -33,6 +33,9 @@ import de.seemoo.at_tracking_detection.util.Utility
import de.seemoo.at_tracking_detection.util.ble.BluetoothConstants
import de.seemoo.at_tracking_detection.util.ble.BluetoothLeService
import kotlinx.coroutines.launch
+import org.osmdroid.events.MapListener
+import org.osmdroid.events.ScrollEvent
+import org.osmdroid.events.ZoomEvent
import org.osmdroid.views.MapView
import timber.log.Timber
import java.util.concurrent.TimeUnit
@@ -48,6 +51,8 @@ class TrackingFragment : Fragment() {
private val safeArgs: TrackingFragmentArgs by navArgs()
+ private lateinit var mapView: MapView
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@@ -78,6 +83,9 @@ class TrackingFragment : Fragment() {
@SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onResume() {
super.onResume()
+
+ zoomToMarkers()
+
val activity = ATTrackingDetectionApplication.getCurrentActivity() ?: return
LocalBroadcastManager.getInstance(activity)
@@ -120,6 +128,24 @@ class TrackingFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ mapView = view.findViewById(R.id.map)
+
+ mapView.addMapListener(object : MapListener {
+ override fun onZoom(event: ZoomEvent?): Boolean {
+ // Check if the map is fully loaded and ready for zoom operations
+ if (mapView.zoomLevelDouble >= 0 && mapView.zoomLevelDouble <= mapView.maxZoomLevel) {
+ // Call the method to zoom to the bounding box of markers
+ zoomToMarkers()
+ }
+ return true
+ }
+
+ override fun onScroll(event: ScrollEvent?): Boolean {
+ // Handle scroll event if needed
+ return true
+ }
+ })
+
trackingViewModel.deviceType.observe(viewLifecycleOwner) { deviceType ->
view.findViewById(R.id.identifier_explanation).text =
Utility.getExplanationTextForDeviceType(deviceType)
@@ -149,14 +175,13 @@ class TrackingFragment : Fragment() {
handlePlaySound()
}
- val map: MapView = view.findViewById(R.id.map)
- Utility.enableMyLocationOverlay(map)
+ Utility.enableMyLocationOverlay(mapView)
trackingViewModel.markerLocations.observe(viewLifecycleOwner) { beacons ->
lifecycleScope.launch {
trackingViewModel.isMapLoading.postValue(true)
val locationList = Utility.fetchLocationListFromBeaconList(beacons)
- Utility.setGeoPointsFromListOfLocations(locationList, map)
+ Utility.setGeoPointsFromListOfLocations(locationList, mapView)
trackingViewModel.isMapLoading.postValue(false)
}
}
@@ -176,6 +201,15 @@ class TrackingFragment : Fragment() {
addInteractions(view)
}
+ private fun zoomToMarkers() {
+ lifecycleScope.launch {
+ trackingViewModel.isMapLoading.postValue(true)
+ val locationList = Utility.fetchLocationListFromBeaconList(trackingViewModel.markerLocations.value ?: emptyList())
+ Utility.setGeoPointsFromListOfLocations(locationList, mapView)
+ trackingViewModel.isMapLoading.postValue(false)
+ }
+ }
+
private fun navigateToFeedbackFragment() {
val directions: NavDirections =
TrackingFragmentDirections.actionTrackingFragmentToFeedbackFragment(notificationId)
From 559d298e579abcc1392639dba8d7fa58d57ac5ca Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 17 Mar 2024 14:25:58 +0100
Subject: [PATCH 102/154] automatic adjustments to the code
---
.../ATTrackingDetectionApplication.kt | 6 +++---
.../database/AppDatabase.kt | 4 +---
.../database/Converters.kt | 1 -
.../database/daos/NotificationDao.kt | 9 +++++++--
.../database/daos/ScanDao.kt | 11 ++++++----
.../at_tracking_detection/hilt/ApiModule.kt | 2 +-
.../hilt/DatabaseModule.kt | 8 ++++----
.../notifications/NotificationBuilder.kt | 17 ++++++----------
.../ScheduledNotificationReceiver.kt | 1 -
.../ui/dashboard/DeviceMapFragment.kt | 2 +-
.../ui/dashboard/RiskRowViewModel.kt | 7 ++-----
.../ui/debug/DebugLogViewModel.kt | 5 ++---
.../ui/debug/DebugScanViewModel.kt | 3 +--
.../ui/debug/DebugScansFragment.kt | 18 +++++------------
.../ui/debug/DebugViewModel.kt | 4 +---
.../ui/devices/AllDevicesFragment.kt | 4 ----
.../ui/devices/DeviceAdapter.kt | 2 +-
.../devices/filter/models/DeviceTypeFilter.kt | 4 +---
.../ui/onboarding/LocationFragment.kt | 20 +++++++++----------
.../ui/onboarding/ShareDataFragment.kt | 4 ----
.../ui/scan/BluetoothDeviceAdapter.kt | 2 +-
.../ui/scan/dialog/DialogViewModel.kt | 7 +++----
.../ui/scan/dialog/PlaySoundDialogFragment.kt | 2 +-
.../ui/settings/AttributionAdapter.kt | 3 ++-
.../ui/settings/DataDeletionFragment.kt | 3 +--
.../ui/settings/InformationFragment.kt | 1 -
.../ui/tracking/ObserveTrackerViewModel.kt | 3 +--
.../util/BindingAdapter.kt | 5 ++---
.../at_tracking_detection/util/Utility.kt | 2 +-
.../util/ble/BLEScanner.kt | 14 ++++---------
.../util/ble/OpportunisticBLEScanner.kt | 4 ++--
.../worker/BackgroundWorkScheduler.kt | 7 +++++--
app/src/main/res/layout/fragment_scan.xml | 5 +++--
app/src/main/res/values-de/strings.xml | 8 ++++++--
app/src/main/res/values/strings.xml | 5 +++++
build.gradle | 2 +-
36 files changed, 91 insertions(+), 114 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt
index 630c765b..4c464946 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt
@@ -178,11 +178,11 @@ class ATTrackingDetectionApplication : Application(), Configuration.Provider {
null
}
}
- fun getCurrentApp(): ATTrackingDetectionApplication? {
+ fun getCurrentApp(): ATTrackingDetectionApplication {
return instance
}
//TODO: Add real survey URL
- val SURVEY_URL = "https://survey.seemoo.tu-darmstadt.de/index.php/117478?G06Q39=AirGuardAppAndroid&newtest=Y&lang=en"
- val SURVEY_IS_RUNNING = false
+ const val SURVEY_URL = "https://survey.seemoo.tu-darmstadt.de/index.php/117478?G06Q39=AirGuardAppAndroid&newtest=Y&lang=en"
+ const val SURVEY_IS_RUNNING = false
}
}
\ No newline at end of file
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 01f102d3..3e9fd398 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
@@ -51,7 +51,5 @@ abstract class AppDatabase : RoomDatabase() {
fromColumnName = "date",
toColumnName = "endDate"
)
- class RenameScanMigrationSpec: AutoMigrationSpec {
-
- }
+ class RenameScanMigrationSpec: AutoMigrationSpec
}
\ No newline at end of file
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
index f43b156d..c7b81735 100644
--- 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
@@ -1,7 +1,6 @@
package de.seemoo.at_tracking_detection.database
import androidx.room.TypeConverter
-import java.time.LocalDateTime
class Converters {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/NotificationDao.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/NotificationDao.kt
index 50750aae..7cdade7b 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/NotificationDao.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/NotificationDao.kt
@@ -1,8 +1,13 @@
package de.seemoo.at_tracking_detection.database.daos
-import androidx.room.*
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.RewriteQueriesToDropUnusedColumns
+import androidx.room.Transaction
+import androidx.room.Update
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.database.relations.NotificationFeedback
import kotlinx.coroutines.flow.Flow
import java.time.LocalDateTime
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/ScanDao.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/ScanDao.kt
index 59157f54..8591e6c6 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/ScanDao.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/ScanDao.kt
@@ -1,11 +1,14 @@
package de.seemoo.at_tracking_detection.database.daos
-import androidx.room.*
-import de.seemoo.at_tracking_detection.database.models.Beacon
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Update
import de.seemoo.at_tracking_detection.database.models.Scan
-import java.time.LocalDateTime
import kotlinx.coroutines.flow.Flow
-import kotlin.reflect.jvm.internal.impl.descriptors.Visibilities
+import java.time.LocalDateTime
@Dao
interface ScanDao {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/hilt/ApiModule.kt b/app/src/main/java/de/seemoo/at_tracking_detection/hilt/ApiModule.kt
index ae5f3488..7ac60a54 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/hilt/ApiModule.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/hilt/ApiModule.kt
@@ -20,7 +20,7 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object ApiModule {
- val HTTP_TIMEOUT: Long = 60
+ private const val HTTP_TIMEOUT: Long = 60
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder()
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 5aaff441..e3ad2a3d 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
@@ -27,7 +27,7 @@ object DatabaseModule {
try {
db.execSQL("ALTER TABLE `beacon` ADD COLUMN `serviceUUIDs` TEXT DEFAULT NULL")
}catch (e: SQLiteException) {
- Timber.e("Could not create new column ${e}")
+ Timber.e("Could not create new column $e")
}
}
@@ -46,7 +46,7 @@ object DatabaseModule {
db.execSQL("CREATE UNIQUE INDEX `index_location_latitude_longitude` ON `location` (`latitude`, `longitude`)")
db.execSQL("ALTER TABLE `beacon` ADD COLUMN `locationId` INTEGER")
}catch (e: SQLiteException) {
- Timber.e("Could not create location ${e}")
+ Timber.e("Could not create location $e")
}
var sql: String
@@ -138,7 +138,7 @@ object DatabaseModule {
db.execSQL("INSERT INTO `beacon_backup` SELECT `beaconId`, `receivedAt`, `rssi`, `deviceAddress`, `locationId`, `mfg`, `serviceUUIDs` FROM `beacon`")
db.execSQL("DROP TABLE `beacon`")
} catch (e: SQLiteException) {
- Timber.e("Could not create beacon_backup ${e}")
+ Timber.e("Could not create beacon_backup $e")
}
try {
@@ -146,7 +146,7 @@ object DatabaseModule {
db.execSQL("INSERT INTO `beacon` SELECT `beaconId`, `receivedAt`, `rssi`, `deviceAddress`, `locationId`, `mfg`, `serviceUUIDs` FROM `beacon_backup`")
db.execSQL("DROP TABLE `beacon_backup`")
} catch (e: SQLiteException) {
- Timber.e("Could not create beacon ${e}")
+ Timber.e("Could not create beacon $e")
}
}
}
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 78714597..b12a4365 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
@@ -123,7 +123,7 @@ class NotificationBuilder @Inject constructor(
)
)
- val deviceRepository = ATTrackingDetectionApplication.getCurrentApp()?.deviceRepository!!
+ val deviceRepository = ATTrackingDetectionApplication.getCurrentApp().deviceRepository
val device = deviceRepository.getDevice(deviceAddress)
if (device?.deviceType != null && device.deviceType.canBeIgnored()) {
@@ -229,16 +229,11 @@ class NotificationBuilder @Inject constructor(
val bundle: Bundle = packBundle(deviceAddress, notificationId)
val notifyText = if (observationPositive) {
- if (observationDuration == 1L) {
- context.getString(
- R.string.notification_observe_tracker_positive_singular,
- )
- } else {
- context.getString(
- R.string.notification_observe_tracker_positive_plural,
- observationDuration
- )
- }
+ context.resources.getQuantityString(
+ R.plurals.notification_observe_tracker_positive,
+ observationDuration.toInt(),
+ observationDuration
+ )
} else {
context.getString(
R.string.notification_observe_tracker_negative,
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/notifications/ScheduledNotificationReceiver.kt b/app/src/main/java/de/seemoo/at_tracking_detection/notifications/ScheduledNotificationReceiver.kt
index 7be05afc..6b837113 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/notifications/ScheduledNotificationReceiver.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/notifications/ScheduledNotificationReceiver.kt
@@ -3,7 +3,6 @@ package de.seemoo.at_tracking_detection.notifications
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.util.SharedPrefs
import timber.log.Timber
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
index f531fa74..7eea4c49 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
@@ -68,7 +68,7 @@ class DeviceMapFragment : Fragment() {
locationLiveData.observe(viewLifecycleOwner) { locations ->
lifecycleScope.launch {
- val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return@launch
+ val locationRepository = ATTrackingDetectionApplication.getCurrentApp().locationRepository
val locationList: List = if (deviceAddress.isNullOrEmpty()) {
locationRepository.locationsSince(RiskLevelEvaluator.relevantTrackingDateForRiskCalculation)
} else {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskRowViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskRowViewModel.kt
index 47c59c9f..95c0e5ae 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskRowViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/RiskRowViewModel.kt
@@ -1,12 +1,9 @@
package de.seemoo.at_tracking_detection.ui.dashboard
import android.graphics.drawable.Drawable
-import androidx.lifecycle.ViewModel
-import dagger.hilt.android.lifecycle.HiltViewModel
-import javax.inject.Inject
-class RiskRowViewModel constructor(
+class RiskRowViewModel(
val text: String,
val image: Drawable
-) {}
\ No newline at end of file
+)
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugLogViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugLogViewModel.kt
index 8f03d92e..2f96b352 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugLogViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugLogViewModel.kt
@@ -4,7 +4,6 @@ import android.text.Editable
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
-import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import fr.bipi.tressence.file.FileLoggerTree
import timber.log.Timber
import java.io.File
@@ -12,11 +11,11 @@ import javax.inject.Inject
@HiltViewModel
class DebugLogViewModel @Inject constructor(): ViewModel() {
- var fullLogText: List
+ private var fullLogText: List
var logText: MutableLiveData = MutableLiveData()
var filterText: MutableLiveData = MutableLiveData()
- val logFile: File
+ private val logFile: File
init {
val trees = Timber.forest()
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugScanViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugScanViewModel.kt
index 311f3a72..8ed8f545 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugScanViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugScanViewModel.kt
@@ -6,7 +6,6 @@ import androidx.lifecycle.asLiveData
import dagger.hilt.android.lifecycle.HiltViewModel
import de.seemoo.at_tracking_detection.database.models.Scan
import de.seemoo.at_tracking_detection.database.repository.ScanRepository
-import java.time.LocalDateTime
import javax.inject.Inject
@HiltViewModel
@@ -14,7 +13,7 @@ class DebugScanViewModel @Inject constructor(
scanRepository: ScanRepository
): ViewModel() {
- val scansLive: LiveData>
+ private val scansLive: LiveData>
val scans: List
init {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugScansFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugScansFragment.kt
index a195d52b..83a1afa8 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugScansFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugScansFragment.kt
@@ -1,11 +1,14 @@
package de.seemoo.at_tracking_detection.ui.debug
-import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Divider
@@ -20,24 +23,13 @@ import androidx.compose.ui.unit.Dp
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.asFlow
-import androidx.lifecycle.asLiveData
import com.google.android.material.composethemeadapter.MdcTheme
import dagger.hilt.android.AndroidEntryPoint
-import dagger.hilt.android.lifecycle.HiltViewModel
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.Scan
-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.ScanRepository
import de.seemoo.at_tracking_detection.databinding.FragmentDebugScansBinding
-import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
-import java.text.DateFormat
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
-import javax.inject.Inject
@AndroidEntryPoint
class DebugScansFragment: Fragment() {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugViewModel.kt
index d25ad3de..fbe6bc46 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugViewModel.kt
@@ -2,10 +2,8 @@ package de.seemoo.at_tracking_detection.ui.debug
import android.content.SharedPreferences
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
-import de.seemoo.at_tracking_detection.database.repository.DeviceRepository
import de.seemoo.at_tracking_detection.util.SharedPrefs
import javax.inject.Inject
@@ -32,7 +30,7 @@ class DebugViewModel @Inject constructor(
updateScanText()
}
- fun updateScanText() {
+ private fun updateScanText() {
if (SharedPrefs.isScanningInBackground) {
scanText.postValue("Scanning in background")
}else {
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 d7d2bd3d..30e4b931 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
@@ -28,10 +28,6 @@ class AllDevicesFragment : Fragment() {
private val viewModel: AllDevicesViewModel by viewModels()
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- }
-
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DeviceAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DeviceAdapter.kt
index 03c544cb..da901e55 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DeviceAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DeviceAdapter.kt
@@ -10,7 +10,7 @@ import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.databinding.ItemDeviceBinding
-class DeviceAdapter constructor(
+class DeviceAdapter(
private val devicesViewModel: DevicesViewModel,
private val onClickListener: OnClickListener
) :
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/filter/models/DeviceTypeFilter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/filter/models/DeviceTypeFilter.kt
index 16fae1d2..bce9c7be 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/filter/models/DeviceTypeFilter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/filter/models/DeviceTypeFilter.kt
@@ -1,7 +1,6 @@
package de.seemoo.at_tracking_detection.ui.devices.filter.models
import androidx.collection.ArraySet
-import androidx.collection.arraySetOf
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.database.models.device.DeviceType
@@ -18,10 +17,9 @@ class DeviceTypeFilter(deviceTypes: Set) : Filter() {
fun remove(deviceType: DeviceType) = deviceTypes.remove(deviceType)
- var deviceTypes: ArraySet
+ var deviceTypes: ArraySet = ArraySet()
init {
- this.deviceTypes = ArraySet()
this.deviceTypes.addAll(deviceTypes)
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/LocationFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/LocationFragment.kt
index 73a82b8d..404380c0 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/LocationFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/LocationFragment.kt
@@ -17,13 +17,13 @@ import de.seemoo.at_tracking_detection.R
@AndroidEntryPoint
class LocationFragment : Fragment(R.layout.fragment_location_permission), SlidePolicy {
- var canContinue = true
+ private var canContinue = true
// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of
// ActivityResultLauncher. You can use either a val, as shown in this snippet,
// or a lateinit var in your onAttach() or onCreate() method.
- val requestPermissionLauncher =
+ private val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
@@ -61,21 +61,21 @@ class LocationFragment : Fragment(R.layout.fragment_location_permission), SlideP
showAlertDialogForLocationPermission()
}
- fun showAlertDialogForLocationPermission() {
- val builder: AlertDialog.Builder? = context.let { AlertDialog.Builder(it) }
+ private fun showAlertDialogForLocationPermission() {
+ val builder: AlertDialog.Builder = context.let { AlertDialog.Builder(it) }
- builder?.setMessage(R.string.location_permission_message)
- builder?.setIcon(R.drawable.ic_baseline_location_on_24)
- builder?.setTitle(R.string.location_permission_title)
- builder?.setPositiveButton(R.string.ok_button) { _: DialogInterface, _: Int ->
+ builder.setMessage(R.string.location_permission_message)
+ builder.setIcon(R.drawable.ic_baseline_location_on_24)
+ builder.setTitle(R.string.location_permission_title)
+ builder.setPositiveButton(R.string.ok_button) { _: DialogInterface, _: Int ->
this.requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
- builder?.setNegativeButton("Cancel") { _: DialogInterface, _:Int ->
+ builder.setNegativeButton("Cancel") { _: DialogInterface, _:Int ->
}
- val dialog = builder?.create()
+ val dialog = builder.create()
dialog?.show()
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/ShareDataFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/ShareDataFragment.kt
index 0a9af334..659d13b1 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/ShareDataFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/onboarding/ShareDataFragment.kt
@@ -15,11 +15,7 @@ import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.databinding.FragmentShareDataBinding
import de.seemoo.at_tracking_detection.statistics.api.Api
-import de.seemoo.at_tracking_detection.util.SharedPrefs
import de.seemoo.at_tracking_detection.worker.BackgroundWorkScheduler
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
index 51ca7c02..3d48d00a 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
@@ -36,7 +36,7 @@ class BluetoothDeviceAdapter:
holder.bind(scanResult)
holder.itemView.findViewById(R.id.scan_result_item_card)
- .setOnClickListener() {
+ .setOnClickListener {
val deviceAddress: String = getPublicKey(scanResult)
val directions = ScanFragmentDirections.actionScanToTrackingFragment(deviceAddress)
holder.itemView.findNavController()
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/DialogViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/DialogViewModel.kt
index e988ea83..78a5db95 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/DialogViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/DialogViewModel.kt
@@ -2,17 +2,16 @@ package de.seemoo.at_tracking_detection.ui.scan.dialog
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
class DialogViewModel : ViewModel() {
val playSoundState = MutableStateFlow(ConnectionState.Connecting)
sealed class ConnectionState {
- object Success : ConnectionState()
+ data object Success : ConnectionState()
data class Error(val message: String) : ConnectionState()
- object Connecting : ConnectionState()
- object Playing : ConnectionState()
+ data object Connecting : ConnectionState()
+ data object Playing : ConnectionState()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/PlaySoundDialogFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/PlaySoundDialogFragment.kt
index 9bcc07b3..34bc4cbc 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/PlaySoundDialogFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/dialog/PlaySoundDialogFragment.kt
@@ -25,7 +25,7 @@ import de.seemoo.at_tracking_detection.util.ble.BluetoothLeService
import kotlinx.coroutines.launch
import timber.log.Timber
-class PlaySoundDialogFragment constructor(scanResult: ScanResult) : BottomSheetDialogFragment() {
+class PlaySoundDialogFragment(scanResult: ScanResult) : BottomSheetDialogFragment() {
private val viewModel: DialogViewModel by viewModels()
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/AttributionAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/AttributionAdapter.kt
index 51cd5b08..d1808972 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/AttributionAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/AttributionAdapter.kt
@@ -1,10 +1,11 @@
+package de.seemoo.at_tracking_detection.ui.settings
+
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import de.seemoo.at_tracking_detection.R
-import de.seemoo.at_tracking_detection.ui.settings.AttributionItem
class AttributionAdapter(
private val attributions: List,
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 2bb608bf..d223c8f4 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
@@ -32,8 +32,7 @@ class DataDeletionFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
- val rootView = inflater.inflate(R.layout.fragment_data_deletion, container, false)
- return rootView
+ return inflater.inflate(R.layout.fragment_data_deletion, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/InformationFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/InformationFragment.kt
index c843677a..0f573e5c 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/InformationFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/settings/InformationFragment.kt
@@ -1,6 +1,5 @@
package de.seemoo.at_tracking_detection.ui.settings
-import AttributionAdapter
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerViewModel.kt
index d4734eb0..043bd13d 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/ObserveTrackerViewModel.kt
@@ -2,5 +2,4 @@ package de.seemoo.at_tracking_detection.ui.tracking
import androidx.lifecycle.ViewModel
-class ObserveTrackerViewModel: ViewModel() {
-}
\ No newline at end of file
+class ObserveTrackerViewModel: ViewModel()
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
index 3a340a19..d4825929 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
@@ -10,7 +10,6 @@ import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
-import java.util.*
@BindingAdapter("setAdapter")
fun RecyclerView.bindRecyclerViewAdapter(adapter: RecyclerView.Adapter<*>) {
@@ -42,8 +41,8 @@ fun setDeviceDrawable(imageView: ImageView, scanResult: ScanResult) {
@BindingAdapter("setDeviceName", requireAll = true)
fun setDeviceName (textView: TextView, scanResult: ScanResult) {
- val deviceRepository = ATTrackingDetectionApplication.getCurrentApp()?.deviceRepository
- val deviceFromDb = deviceRepository?.getDevice(getPublicKey(scanResult))
+ val deviceRepository = ATTrackingDetectionApplication.getCurrentApp().deviceRepository
+ val deviceFromDb = deviceRepository.getDevice(getPublicKey(scanResult))
if (deviceFromDb?.name != null) {
textView.text = deviceFromDb.getDeviceNameWithID()
} else {
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 bcc2287a..d1844088 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
@@ -180,7 +180,7 @@ object Utility {
.filter { it.locationId != null && it.locationId != 0 } // Filter out invalid locationId entries
val locationList = arrayListOf()
- val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return emptyList()
+ val locationRepository = ATTrackingDetectionApplication.getCurrentApp().locationRepository
uniqueLocations.mapNotNullTo(locationList) {
locationRepository.getLocationWithId(it.locationId!!)
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 3e197abc..8c747e56 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
@@ -29,10 +29,6 @@ object BLEScanner {
// Contains the last 10 scan results
private var scanResults = ArrayList()
- init {
-
- }
-
fun startBluetoothScan(appContext: Context): Boolean {
// Check if already scanning
if(this.bluetoothManager != null && isScanning) { return true }
@@ -140,8 +136,9 @@ object BLEScanner {
private fun fetchCurrentLocation() {
// We fetch the current location and cache for saving the results to the DB
- val locationProvider = ATTrackingDetectionApplication.getCurrentApp()?.locationProvider
- val loc = locationProvider?.lastKnownOrRequestLocationUpdates(locationRequester, timeoutMillis = null)
+ val locationProvider = ATTrackingDetectionApplication.getCurrentApp().locationProvider
+ val loc =
+ locationProvider.lastKnownOrRequestLocationUpdates(locationRequester, timeoutMillis = null)
if (loc != null) {
this.lastLocation = loc
}
@@ -155,10 +152,7 @@ object BLEScanner {
fun isBluetoothOn(): Boolean {
val adapter = bluetoothManager?.adapter
- if (adapter != null && adapter.isEnabled) {
- return true
- }
- return false
+ return adapter != null && adapter.isEnabled
}
fun openBluetoothSettings(context: Context) {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/OpportunisticBLEScanner.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/OpportunisticBLEScanner.kt
index f9f3d5c8..ec598d84 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/OpportunisticBLEScanner.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/OpportunisticBLEScanner.kt
@@ -31,7 +31,7 @@ class OpportunisticBLEScanner(var notificationService: NotificationService?) {
init {
if (notificationService == null) {
notificationService =
- ATTrackingDetectionApplication.getCurrentApp()?.notificationService
+ ATTrackingDetectionApplication.getCurrentApp().notificationService
}
val context = ATTrackingDetectionApplication.getAppContext()
@@ -97,7 +97,7 @@ class OpportunisticBLEScanner(var notificationService: NotificationService?) {
val millisecondsSinceEvent = (SystemClock.elapsedRealtimeNanos() - scanResult.timestampNanos) / 1000000L
val timeOfEvent = System.currentTimeMillis() - millisecondsSinceEvent
val eventDate = Instant.ofEpochMilli(timeOfEvent).atZone(ZoneId.systemDefault()).toLocalDateTime()
- Timber.d("Scan received at ${eventDate.toString()}")
+ Timber.d("Scan received at $eventDate")
if (BuildConfig.DEBUG) {
notificationService?.sendDebugNotificationFoundDevice(scanResult)
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
index fe9a35b2..6b7ed2f5 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
@@ -8,9 +8,12 @@ import android.content.Intent
import android.os.Build
import androidx.lifecycle.LiveData
import androidx.lifecycle.map
-import androidx.work.*
+import androidx.work.ExistingPeriodicWorkPolicy
+import androidx.work.ExistingWorkPolicy
+import androidx.work.Operation
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
-import de.seemoo.at_tracking_detection.BuildConfig
import timber.log.Timber
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml
index 20e515c7..750e0720 100644
--- a/app/src/main/res/layout/fragment_scan.xml
+++ b/app/src/main/res/layout/fragment_scan.xml
@@ -174,12 +174,13 @@
android:id="@+id/info_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minHeight="48dp"
+ android:contentDescription="@string/info_button_description"
android:src="@drawable/ic_baseline_info_24"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:padding="8dp"
- android:background="@android:color/transparent"
- android:onClick="toggleInfoLayout" />
+ android:background="@android:color/transparent" />
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 569fb065..64c22e6c 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -137,8 +137,10 @@
Verbinden mit Bluetooth-Geräten
Wenn ein Tracking-Gerät gefunden wird, gibt AirGuard Dir die Möglichkeit, einen Ton darauf abzuspielen. Dafür benötigen wir die Erlaubnis, uns mit diesem Gerät zu verbinden!
Dein beobachteter Tracker
- Ein Tracker den du beobachtet hast, folgt dir seit mindestens eine Stunde.
- Ein Tracker den du beobachtet hast, folgt dir seit mindestens %d Stunden.
+
+ - Ein Tracker den du beobachtet hast, folgt dir seit mindestens eine Stunde.
+ - Ein Tracker den du beobachtet hast, folgt dir seit mindestens %d Stunden.
+
Ein Tracker den du beobachtet hast, folgt dir nicht.
Es ist ein Fehler aufgetreten. Versuche es nochmal.
Senden von Benachrichtigungen
@@ -340,6 +342,8 @@
Achtung:\nChipolo Geräte ändern ihre Kennung nicht.\nDies ermöglicht zwar eine genauere Tracking Erkennung, führt aber auch zu mehr falschen Warnungen.\nFalls sie über einen Chipolo-Tracker gewarnt werden, überprüfen Sie ob der Tracker nicht von jemanden aus ihrer Umgebung (Freunde, Familie, Kollegen, Nachbarn) stammt.
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.
+ Zeigt Erklärtext
+
Mögliche Tracker
Sichere Tracker
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b544b51e..337454a1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -133,6 +133,10 @@
Your observed tracker
A tracker you observed has been following you for at least an hour.
A tracker you observed has been following you for at least %d hours.
+
+ - A tracker you observed has been following you for at least one hour.
+ - A tracker you observed has been following you for at least %d hours.
+
A tracker you observed is not following you.
An error occurred while observing this tracker. Please try again.
@@ -331,6 +335,7 @@
Contact Us
Preview Image for this article
+ Show explanation text
Observe Tracker
Image of a clock
diff --git a/build.gradle b/build.gradle
index 14f36fa3..6a0e058e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -34,5 +34,5 @@ allprojects {
}
tasks.register('clean', Delete) {
- delete rootProject.buildDir
+ delete project.layout.buildDirectory
}
\ No newline at end of file
From 2f062e22943e235ebfd6df99ebb28600625a557f Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 17 Mar 2024 21:36:18 +0100
Subject: [PATCH 103/154] fix a bug where markers were not displayed correctly
when accessed via a notification
---
.../at_tracking_detection/ui/tracking/TrackingFragment.kt | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 3cb8d4e9..b9f1a21b 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -146,6 +146,10 @@ class TrackingFragment : Fragment() {
}
})
+ if (trackingViewModel.markerLocations.value != null && !trackingViewModel.isMapLoading.value!!) {
+ zoomToMarkers()
+ }
+
trackingViewModel.deviceType.observe(viewLifecycleOwner) { deviceType ->
view.findViewById(R.id.identifier_explanation).text =
Utility.getExplanationTextForDeviceType(deviceType)
From 98004f7f928e24780ca47a8db536d17350a831a6 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 17 Mar 2024 21:59:09 +0100
Subject: [PATCH 104/154] optimize retrieval of markers from the database
---
.../database/daos/LocationDao.kt | 6 +++++
.../database/repository/LocationRepository.kt | 5 ++++
.../ui/dashboard/DeviceMapFragment.kt | 27 +++++++------------
3 files changed, 21 insertions(+), 17 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/LocationDao.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/LocationDao.kt
index a23da719..8e11b224 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/LocationDao.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/LocationDao.kt
@@ -31,6 +31,12 @@ interface LocationDao {
@Query("SELECT * FROM location WHERE locationId NOT IN (SELECT DISTINCT locationId FROM beacon)")
fun getLocationsWithNoBeacons(): List
+ @Query("SELECT l.* FROM location l INNER JOIN beacon b ON l.locationId = b.locationId WHERE b.deviceAddress = :deviceAddress")
+ fun getLocationsForDevice(deviceAddress: String): List
+
+ @Query("SELECT l.* FROM location l INNER JOIN beacon b ON l.locationId = b.locationId WHERE b.deviceAddress = :deviceAddress AND b.receivedAt >= :since")
+ fun getLocationsForDeviceSince(deviceAddress: String, since: LocalDateTime): List
+
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(location: LocationModel): Long
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/LocationRepository.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/LocationRepository.kt
index ed123420..07d066c0 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/LocationRepository.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/repository/LocationRepository.kt
@@ -24,6 +24,11 @@ class LocationRepository @Inject constructor(
fun getNumberOfBeaconsForLocation(locationId: Int): Int = locationDao.getNumberOfBeaconsForLocation(locationId)
+ fun getLocationsForBeacon(deviceAddress: String): List = locationDao.getLocationsForDevice(deviceAddress)
+
+ fun getLocationsForBeaconSince(deviceAddress: String, since: LocalDateTime): List = locationDao.getLocationsForDeviceSince(deviceAddress, since)
+
+
fun getLocationsWithNoBeacons(): List = locationDao.getLocationsWithNoBeacons()
@WorkerThread
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
index 7eea4c49..0cf77a13 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
@@ -9,13 +9,11 @@ import androidx.core.view.ViewCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
-import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
-import de.seemoo.at_tracking_detection.database.models.Beacon
import de.seemoo.at_tracking_detection.database.models.Location
import de.seemoo.at_tracking_detection.databinding.FragmentDeviceMapBinding
import de.seemoo.at_tracking_detection.util.Utility
@@ -60,23 +58,18 @@ class DeviceMapFragment : Fragment() {
viewModel.isMapLoading.postValue(true)
Utility.enableMyLocationOverlay(map)
- val locationLiveData: LiveData> = if (deviceAddress.isNullOrEmpty()) {
- viewModel.allLocations
- } else {
- viewModel.markerLocations
- }
-
- locationLiveData.observe(viewLifecycleOwner) { locations ->
- lifecycleScope.launch {
- val locationRepository = ATTrackingDetectionApplication.getCurrentApp().locationRepository
- val locationList: List = if (deviceAddress.isNullOrEmpty()) {
- locationRepository.locationsSince(RiskLevelEvaluator.relevantTrackingDateForRiskCalculation)
- } else {
- Utility.fetchLocationListFromBeaconList(locations)
- }
+ lifecycleScope.launch {
+ val locationRepository = ATTrackingDetectionApplication.getCurrentApp().locationRepository
+ val relevantTrackingDate = RiskLevelEvaluator.relevantTrackingDateForRiskCalculation
+ val locationList: List = if (!deviceAddress.isNullOrEmpty()) {
+ locationRepository.getLocationsForBeaconSince(deviceAddress!!, relevantTrackingDate)
+ } else {
+ locationRepository.locationsSince(relevantTrackingDate)
+ }
+ try {
Utility.setGeoPointsFromListOfLocations(locationList, map)
- }.invokeOnCompletion {
+ } finally {
viewModel.isMapLoading.postValue(false)
}
}
From 884e5099aed37a80ca569f8cef84999dacd0a325 Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Fri, 22 Mar 2024 11:29:31 +0100
Subject: [PATCH 105/154] Moving background scans to be scheduled by
AlarmManager instead of background workers. - All Background Scans are now
perform by the BackgroundBluetoothScanner - The ScanBluetoothWorker stays
functional - Tracking Detection is still performed by a worker
---
.../ScanBluetoothWorkerTest.kt | 33 +-
app/src/main/AndroidManifest.xml | 2 +
.../ATTrackingDetectionApplication.kt | 4 +
.../detection/BackgroundBluetoothScanner.kt | 419 ++++++++++++++++++
.../detection/ScanBluetoothWorker.kt | 336 +-------------
.../hilt/DatabaseModule.kt | 4 +-
.../at_tracking_detection/ui/MainActivity.kt | 10 +
.../ui/scan/ScanViewModel.kt | 6 +-
.../util/ble/BLEScanCallback.kt | 9 +-
.../worker/BackgroundWorkBuilder.kt | 5 +
.../worker/BackgroundWorkScheduler.kt | 49 ++
.../worker/ScheduleWorkersReceiver.kt | 68 ++-
.../worker/WorkerConstants.kt | 1 +
13 files changed, 573 insertions(+), 373 deletions(-)
create mode 100644 app/src/main/java/de/seemoo/at_tracking_detection/detection/BackgroundBluetoothScanner.kt
diff --git a/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanBluetoothWorkerTest.kt b/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanBluetoothWorkerTest.kt
index fddaf3f9..9d22f315 100644
--- a/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanBluetoothWorkerTest.kt
+++ b/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanBluetoothWorkerTest.kt
@@ -16,6 +16,7 @@ import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
import androidx.work.testing.TestForegroundUpdater
import androidx.work.testing.TestProgressUpdater
import de.seemoo.at_tracking_detection.database.AppDatabase
+import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner
import de.seemoo.at_tracking_detection.detection.LocationProvider
import de.seemoo.at_tracking_detection.detection.LocationRequester
import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker
@@ -76,9 +77,9 @@ class ScanBluetoothWorkerTest {
val scanRepository = DatabaseModule.provideScanRepository(DatabaseModule.provideScanDao(db))
val locationRepository = DatabaseModule.provideLocationRepository(DatabaseModule.provideLocationDao(db))
val locationProvider = LocationProvider(context.getSystemService()!!)
-
val notificationService = ATTrackingDetectionApplication.getCurrentApp()!!.notificationService
val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()!!.backgroundWorkScheduler
+// val backgroundBluetoothScanner = BackgroundBluetoothScanner(backgroundWorkScheduler, notificationService, locationProvider, scanRepository)
val params = WorkerParameters(
UUID.randomUUID(),
@@ -98,7 +99,7 @@ class ScanBluetoothWorkerTest {
val worker = ScanBluetoothWorker(
context,
params,
- scanRepository, locationProvider, notificationService, backgroundWorkScheduler)
+ backgroundWorkScheduler)
runBlocking {
val result = worker.doWork()
@@ -117,6 +118,7 @@ class ScanBluetoothWorkerTest {
val notificationService = ATTrackingDetectionApplication.getCurrentApp()!!.notificationService
val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()!!.backgroundWorkScheduler
+// val backgroundScanner = BackgroundBluetoothScanner(backgroundWorkScheduler, notificationService, locationProvider, scanRepository)
val params = WorkerParameters(
UUID.randomUUID(),
@@ -133,15 +135,11 @@ class ScanBluetoothWorkerTest {
TestForegroundUpdater()
)
- val worker = ScanBluetoothWorker(
- context,
- params,
- scanRepository, locationProvider, notificationService, backgroundWorkScheduler)
runBlocking {
- val result = worker.doWork()
+ val result = BackgroundBluetoothScanner.scanInBackground(startedFrom = "UnitTest")
assertThat(result, instanceOf(ListenableWorker.Result.Success::class.java))
- Assert.assertNotNull(worker.location)
+ Assert.assertNotNull(BackgroundBluetoothScanner.location)
}
}
@@ -157,6 +155,7 @@ class ScanBluetoothWorkerTest {
val notificationService = ATTrackingDetectionApplication.getCurrentApp()!!.notificationService
val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()!!.backgroundWorkScheduler
+// val backgroundScanner = BackgroundBluetoothScanner(backgroundWorkScheduler, notificationService, locationProvider, scanRepository)
val params = WorkerParameters(
UUID.randomUUID(),
@@ -173,15 +172,10 @@ class ScanBluetoothWorkerTest {
TestForegroundUpdater()
)
- val worker = ScanBluetoothWorker(
- context,
- params,
- scanRepository, locationProvider, notificationService, backgroundWorkScheduler)
-
runBlocking {
- val result = worker.doWork()
+ val result = BackgroundBluetoothScanner.scanInBackground(startedFrom = "UnitTest")
assertThat(result, instanceOf(ListenableWorker.Result.Success::class.java))
- Assert.assertNull(worker.location)
+ Assert.assertNull(BackgroundBluetoothScanner.location)
}
}
@@ -198,6 +192,7 @@ class ScanBluetoothWorkerTest {
val notificationService = ATTrackingDetectionApplication.getCurrentApp()!!.notificationService
val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()!!.backgroundWorkScheduler
+
val params = WorkerParameters(
UUID.randomUUID(),
Data.EMPTY,
@@ -213,15 +208,11 @@ class ScanBluetoothWorkerTest {
TestForegroundUpdater()
)
- val worker = ScanBluetoothWorker(
- context,
- params,
- scanRepository, locationProvider, notificationService, backgroundWorkScheduler)
runBlocking {
- val result = worker.doWork()
+ val result = BackgroundBluetoothScanner.scanInBackground(startedFrom = "UnitTest")
assertThat(result, instanceOf(ListenableWorker.Result.Success::class.java))
- Assert.assertNull(worker.location)
+ Assert.assertNull(BackgroundBluetoothScanner.location)
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5c300e76..ea607e8f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -33,6 +33,8 @@
+
+
= ConcurrentHashMap()
+
+ private var applicationContext: Context = ATTrackingDetectionApplication.getAppContext()
+
+ var location: android.location.Location? = null
+ set(value) {
+ field = value
+ if (value != null) {
+ locationRetrievedCallback?.let { it() }
+ }
+ }
+
+ private var locationRetrievedCallback: (() -> Unit)? = null
+
+ private var locationFetchStarted: Long? = null
+
+ val backgroundWorkScheduler: BackgroundWorkScheduler?
+ get() {
+ return ATTrackingDetectionApplication.getCurrentApp()?.backgroundWorkScheduler
+ }
+
+ val notificationService: NotificationService?
+ get() {
+ return ATTrackingDetectionApplication.getCurrentApp()?.notificationService
+ }
+ val locationProvider: LocationProvider?
+ get() {
+ return ATTrackingDetectionApplication.getCurrentApp()?.locationProvider
+ }
+
+ val scanRepository: ScanRepository?
+ get() {
+ return ATTrackingDetectionApplication.getCurrentApp()?.scanRepository
+ }
+
+ var isScanning = false
+ class BackgroundScanResults(var duration: Long, var scanMode: Int, var numberDevicesFound: Int, var failed: Boolean)
+ suspend fun scanInBackground(startedFrom: String): BackgroundScanResults {
+ if (isScanning) {
+ Timber.w("BLE background scan already running")
+ return BackgroundScanResults(0, 0, 0, true)
+ }
+
+ Timber.d("Starting BackgroundBluetoothScanner from $startedFrom")
+ val scanMode = getScanMode()
+ val scanId = scanRepository?.insert(Scan(startDate = LocalDateTime.now(), isManual = false, scanMode = scanMode))
+
+
+ if (!Utility.checkBluetoothPermission()) {
+ Timber.d("Permission to perform bluetooth scan missing")
+ return BackgroundScanResults(0, 0, 0, true)
+ }
+ try {
+ val bluetoothManager =
+ applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
+ bluetoothAdapter = bluetoothManager.adapter
+ } catch (e: Throwable) {
+ Timber.e("BluetoothAdapter not found!")
+ return BackgroundScanResults(0, 0, 0, true)
+ }
+
+ scanResultDictionary = ConcurrentHashMap()
+ isScanning = true
+ location = null
+
+ val useLocation = SharedPrefs.useLocationInTrackingDetection
+ if (useLocation) {
+ // Returns the last known location if this matches our requirements or starts new location updates
+ locationFetchStarted = System.currentTimeMillis()
+ location = locationProvider?.lastKnownOrRequestLocationUpdates(locationRequester = locationRequester, timeoutMillis = BackgroundBluetoothScanner.LOCATION_UPDATE_MAX_TIME_MS - 2000L)
+ }
+
+ //Starting BLE Scan
+ Timber.d("Start Scanning for bluetooth le devices...")
+ val scanSettings =
+ ScanSettings.Builder().setScanMode(scanMode).build()
+
+ SharedPrefs.isScanningInBackground = true
+ BLEScanCallback.startScanning(bluetoothAdapter.bluetoothLeScanner, DeviceManager.scanFilter, scanSettings, leScanCallback)
+
+ val scanDuration: Long = getScanDuration()
+ delay(scanDuration)
+ BLEScanCallback.stopScanning(bluetoothAdapter.bluetoothLeScanner)
+ Timber.d("Scanning for bluetooth le devices stopped!. Discovered ${scanResultDictionary.size} devices")
+
+ //Waiting for updated location to come in
+ Timber.d("Waiting for location update")
+ val fetchedLocation = waitForRequestedLocation()
+ Timber.d("Fetched location? $fetchedLocation")
+ if (location == null) {
+ // Get the last location no matter if the requirements match or not
+ location = locationProvider?.getLastLocation(checkRequirements = false)
+ }
+
+ val validDeviceTypes = DeviceType.getAllowedDeviceTypesFromSettings()
+
+ //Adding all scan results to the database after the scan has finished
+ scanResultDictionary.forEach { (_, discoveredDevice) ->
+ val deviceType = DeviceManager.getDeviceType(discoveredDevice.scanResult)
+
+ if (deviceType in validDeviceTypes) {
+ BackgroundBluetoothScanner.insertScanResult(
+ discoveredDevice.scanResult,
+ location?.latitude,
+ location?.longitude,
+ location?.accuracy,
+ discoveredDevice.discoveryDate,
+ )
+ }
+ }
+
+ SharedPrefs.lastScanDate = LocalDateTime.now()
+ SharedPrefs.isScanningInBackground = false
+ if (scanId != null) {
+ val scan = scanRepository?.scanWithId(scanId.toInt())
+ if (scan != null) {
+ scan.endDate = LocalDateTime.now()
+ scan.duration = scanDuration.toInt() / 1000
+ scan.noDevicesFound = scanResultDictionary.size
+ scanRepository?.update(scan)
+ }
+ }
+
+ Timber.d("Scheduling tracking detector worker")
+ backgroundWorkScheduler?.scheduleTrackingDetector()
+ BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
+
+ isScanning = true
+ Timber.d("Finished Background Scan")
+ return BackgroundScanResults(
+ duration = scanDuration,
+ scanMode = scanMode,
+ numberDevicesFound = scanResultDictionary.size,
+ failed = false
+ )
+ }
+
+ private val leScanCallback: ScanCallback = object : ScanCallback() {
+ override fun onScanResult(callbackType: Int, scanResult: ScanResult) {
+ super.onScanResult(callbackType, scanResult)
+ //Checks if the device has been found already
+ if (!scanResultDictionary.containsKey(BaseDevice.getPublicKey(scanResult))) {
+ Timber.d("Found ${scanResult.device.address} at ${LocalDateTime.now()}")
+ scanResultDictionary[BaseDevice.getPublicKey(scanResult)] =
+ DiscoveredDevice(scanResult, LocalDateTime.now())
+ }
+ }
+
+ override fun onScanFailed(errorCode: Int) {
+ super.onScanFailed(errorCode)
+ Timber.e("Bluetooth scan failed $errorCode")
+ if (BuildConfig.DEBUG) {
+ notificationService?.sendBLEErrorNotification()
+ }
+ }
+ }
+
+ private val locationRequester: LocationRequester = object : LocationRequester() {
+ override fun receivedAccurateLocationUpdate(location: android.location.Location) {
+ val started = locationFetchStarted ?: System.currentTimeMillis()
+ Timber.d("Got location in ${(System.currentTimeMillis()-started)/1000}s")
+ this@BackgroundBluetoothScanner.location = location
+ this@BackgroundBluetoothScanner.locationRetrievedCallback?.let { it() }
+ }
+ }
+
+ private fun getScanMode(): Int {
+ val useLowPower = SharedPrefs.useLowPowerBLEScan
+ return if (useLowPower) {
+ ScanSettings.SCAN_MODE_LOW_POWER
+ } else {
+ ScanSettings.SCAN_MODE_LOW_LATENCY
+ }
+ }
+
+ private fun getScanDuration(): Long {
+ val useLowPower = SharedPrefs.useLowPowerBLEScan
+ return if (useLowPower) {
+ 30_000L
+ } else {
+ 20_000L
+ }
+ }
+
+ private suspend fun waitForRequestedLocation(): Boolean {
+ if (location != null || !SharedPrefs.useLocationInTrackingDetection) {
+ //Location already there. Just return
+ return true
+ }
+
+ return suspendCoroutine { cont ->
+ var coroutineFinished = false
+
+ val handler = Handler(Looper.getMainLooper())
+ val runnable = Runnable {
+ if (!coroutineFinished) {
+ coroutineFinished = true
+ locationRetrievedCallback = null
+ Timber.d("Could not get location update in time.")
+ cont.resume(false)
+ }
+ }
+
+ locationRetrievedCallback = {
+ if (!coroutineFinished) {
+ handler.removeCallbacks(runnable)
+ coroutineFinished = true
+ cont.resume(true)
+ }
+ }
+
+ // Fallback if no location is fetched in time
+ val maximumLocationDurationMillis = BackgroundBluetoothScanner.LOCATION_UPDATE_MAX_TIME_MS
+ handler.postDelayed(runnable, maximumLocationDurationMillis)
+ }
+ }
+
+ class DiscoveredDevice(var scanResult: ScanResult, var discoveryDate: LocalDateTime)
+
+ const val MAX_DISTANCE_UNTIL_NEW_LOCATION: Float = 150f // in meters
+ const val TIME_BETWEEN_BEACONS: Long =
+ 15 // 15 minutes until the same beacon gets saved again in the db
+ const val LOCATION_UPDATE_MAX_TIME_MS: Long =
+ 122_000L // Wait maximum 122s to get a location update
+
+ suspend fun insertScanResult(
+ scanResult: ScanResult,
+ latitude: Double?,
+ longitude: Double?,
+ accuracy: Float?,
+ discoveryDate: LocalDateTime,
+ ): Pair {
+ val deviceSaved = saveDevice(scanResult, discoveryDate) ?: return Pair(
+ null,
+ null
+ ) // return when device does not qualify to be saved
+
+ // set locationId to null if gps location could not be retrieved
+ val locId: Int? = saveLocation(latitude, longitude, discoveryDate, accuracy)?.locationId
+
+ val beaconSaved =
+ saveBeacon(scanResult, discoveryDate, locId) ?: return Pair(null, null)
+
+ return Pair(deviceSaved, beaconSaved)
+ }
+
+ private suspend fun saveBeacon(
+ scanResult: ScanResult,
+ discoveryDate: LocalDateTime,
+ locId: Int?
+ ): Beacon? {
+ val beaconRepository =
+ ATTrackingDetectionApplication.getCurrentApp()?.beaconRepository ?: return null
+ val uuids = scanResult.scanRecord?.serviceUuids?.map { it.toString() }?.toList()
+ val uniqueIdentifier = BaseDevice.getPublicKey(scanResult)
+
+ val connectionState: ConnectionState = BaseDevice.getConnectionState(scanResult)
+ val connectionStateString = Utility.connectionStateToString(connectionState)
+
+ var beacon: Beacon? = null
+ val beacons = beaconRepository.getDeviceBeaconsSince(
+ deviceAddress = uniqueIdentifier,
+ since = discoveryDate.minusMinutes(TIME_BETWEEN_BEACONS)
+ ) // sorted by newest first
+
+ if (beacons.isEmpty()) {
+ Timber.d("Add new Beacon to the database!")
+ beacon = if (BuildConfig.DEBUG) {
+ // Save the manufacturer data to the beacon
+ Beacon(
+ discoveryDate, scanResult.rssi, BaseDevice.getPublicKey(scanResult), locId,
+ scanResult.scanRecord?.bytes, uuids, connectionStateString
+ )
+ } else {
+ Beacon(
+ discoveryDate, scanResult.rssi, BaseDevice.getPublicKey(scanResult), locId,
+ null, uuids, connectionStateString
+ )
+ }
+ beaconRepository.insert(beacon)
+ } else if (beacons[0].locationId == null && locId != null && locId != 0) {
+ // Update beacon within the last TIME_BETWEEN_BEACONS minutes with location
+ Timber.d("Beacon already in the database... Adding Location")
+ beacon = beacons[0]
+ beacon.locationId = locId
+ if (beacon.connectionState == "UNKNOWN" && connectionState != ConnectionState.UNKNOWN) {
+ beacon.connectionState = connectionStateString
+ }
+ beaconRepository.update(beacon)
+ }
+
+ Timber.d("Beacon: $beacon")
+
+ return beacon
+ }
+
+ private suspend fun saveDevice(
+ scanResult: ScanResult,
+ discoveryDate: LocalDateTime
+ ): BaseDevice? {
+ val deviceRepository =
+ ATTrackingDetectionApplication.getCurrentApp()?.deviceRepository ?: return null
+
+ val deviceAddress = BaseDevice.getPublicKey(scanResult)
+
+ // Checks if Device already exists in device database
+ var device = deviceRepository.getDevice(deviceAddress)
+ if (device == null) {
+ // Do not Save Samsung Devices
+ device = BaseDevice(scanResult)
+
+ // Check if ConnectionState qualifies Device to be saved
+ // Only Save when Device is offline long enough
+ if (BaseDevice.getConnectionState(scanResult) !in DeviceManager.savedConnectionStates) {
+ Timber.d("Device not in a saved connection state... Skipping!")
+ return null
+ }
+
+ if (BaseDevice.getConnectionState(scanResult) !in DeviceManager.unsafeConnectionState) {
+ Timber.d("Device is safe and will be hidden to the user!")
+ device.safeTracker = true
+ }
+
+ Timber.d("Add new Device to the database!")
+ deviceRepository.insert(device)
+ } else {
+ Timber.d("Device already in the database... Updating the last seen date!")
+ device.lastSeen = discoveryDate
+ deviceRepository.update(device)
+ }
+
+ Timber.d("Device: $device")
+ return device
+ }
+
+ private suspend fun saveLocation(
+ latitude: Double?,
+ longitude: Double?,
+ discoveryDate: LocalDateTime,
+ accuracy: Float?
+ ): Location? {
+ val locationRepository =
+ ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return null
+
+ // set location to null if gps location could not be retrieved
+ var location: Location? = null
+
+ if (latitude != null && longitude != null) {
+ // Get closest location from database
+ location = locationRepository.closestLocation(latitude, longitude)
+
+ var distanceBetweenLocations: Float = Float.MAX_VALUE
+
+ if (location != null) {
+ val locationA = TrackingDetectorWorker.getLocation(latitude, longitude)
+ val locationB =
+ TrackingDetectorWorker.getLocation(location.latitude, location.longitude)
+ distanceBetweenLocations = locationA.distanceTo(locationB)
+ }
+
+ if (location == null || distanceBetweenLocations > MAX_DISTANCE_UNTIL_NEW_LOCATION) {
+ // Create new location entry
+ Timber.d("Add new Location to the database!")
+ location = Location(discoveryDate, longitude, latitude, accuracy)
+ locationRepository.insert(location)
+ } else {
+ // If location is within the set limit, just use that location and update lastSeen
+ Timber.d("Location already in the database... Updating the last seen date!")
+ location.lastSeen = discoveryDate
+ locationRepository.update(location)
+
+ }
+
+ Timber.d("Location: $location")
+ }
+ return location
+ }
+
+}
\ 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 07b43684..420060db 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
@@ -40,347 +40,25 @@ import kotlin.coroutines.suspendCoroutine
class ScanBluetoothWorker @AssistedInject constructor(
@Assisted appContext: Context,
@Assisted workerParams: WorkerParameters,
- private val scanRepository: ScanRepository,
- private val locationProvider: LocationProvider,
- private val notificationService: NotificationService,
- var backgroundWorkScheduler: BackgroundWorkScheduler
+ var backgroundWorkScheduler: BackgroundWorkScheduler,
) :
CoroutineWorker(appContext, workerParams) {
- private lateinit var bluetoothAdapter: BluetoothAdapter
-
- private var scanResultDictionary: ConcurrentHashMap = ConcurrentHashMap()
-
- var location: Location? = null
- set(value) {
- field = value
- if (value != null) {
- locationRetrievedCallback?.let { it() }
- }
- }
-
- private var locationRetrievedCallback: (() -> Unit)? = null
-
- private var locationFetchStarted: Long? = null
override suspend fun doWork(): Result {
- Timber.d("Bluetooth scanning worker started!")
- val scanMode = getScanMode()
- val scanId = scanRepository.insert(Scan(startDate = LocalDateTime.now(), isManual = false, scanMode = scanMode))
-
- if (!Utility.checkBluetoothPermission()) {
- Timber.d("Permission to perform bluetooth scan missing")
- return Result.retry()
- }
- try {
- val bluetoothManager =
- applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
- bluetoothAdapter = bluetoothManager.adapter
- } catch (e: Throwable) {
- Timber.e("BluetoothAdapter not found!")
- return Result.retry()
- }
- scanResultDictionary = ConcurrentHashMap()
+ val results = BackgroundBluetoothScanner.scanInBackground(startedFrom = "ScanBluetoothWorker")
- val useLocation = SharedPrefs.useLocationInTrackingDetection
- if (useLocation) {
- // Returns the last known location if this matches our requirements or starts new location updates
- locationFetchStarted = System.currentTimeMillis()
- location = locationProvider.lastKnownOrRequestLocationUpdates(locationRequester = locationRequester, timeoutMillis = LOCATION_UPDATE_MAX_TIME_MS - 2000L)
- }
-
- //Starting BLE Scan
- Timber.d("Start Scanning for bluetooth le devices...")
- val scanSettings =
- ScanSettings.Builder().setScanMode(scanMode).build()
-
- SharedPrefs.isScanningInBackground = true
- BLEScanCallback.startScanning(bluetoothAdapter.bluetoothLeScanner, DeviceManager.scanFilter, scanSettings, leScanCallback)
-
- val scanDuration: Long = getScanDuration()
- delay(scanDuration)
- BLEScanCallback.stopScanning(bluetoothAdapter.bluetoothLeScanner)
- Timber.d("Scanning for bluetooth le devices stopped!. Discovered ${scanResultDictionary.size} devices")
-
- //Waiting for updated location to come in
- val fetchedLocation = waitForRequestedLocation()
- Timber.d("Fetched location? $fetchedLocation")
- if (location == null) {
- // Get the last location no matter if the requirements match or not
- location = locationProvider.getLastLocation(checkRequirements = false)
- }
-
- val validDeviceTypes = DeviceType.getAllowedDeviceTypesFromSettings()
-
- //Adding all scan results to the database after the scan has finished
- scanResultDictionary.forEach { (_, discoveredDevice) ->
- val deviceType = DeviceManager.getDeviceType(discoveredDevice.scanResult)
-
- if (deviceType in validDeviceTypes) {
- insertScanResult(
- discoveredDevice.scanResult,
- location?.latitude,
- location?.longitude,
- location?.accuracy,
- discoveredDevice.discoveryDate,
- )
- }
- }
-
- SharedPrefs.lastScanDate = LocalDateTime.now()
- SharedPrefs.isScanningInBackground = false
- val scan = scanRepository.scanWithId(scanId.toInt())
- if (scan != null) {
- scan.endDate = LocalDateTime.now()
- scan.duration = scanDuration.toInt() / 1000
- scan.noDevicesFound = scanResultDictionary.size
- scanRepository.update(scan)
+ if (results.failed) {
+ return Result.retry()
}
- Timber.d("Scheduling tracking detector worker")
- backgroundWorkScheduler.scheduleTrackingDetector()
- BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
-
return Result.success(
Data.Builder()
- .putLong("duration", scanDuration)
- .putInt("mode", scanMode)
- .putInt("devicesFound", scanResultDictionary.size)
+ .putLong("duration", results.duration)
+ .putInt("mode", results.scanMode)
+ .putInt("devicesFound", results.numberDevicesFound)
.build()
)
}
-
- private val leScanCallback: ScanCallback = object : ScanCallback() {
- override fun onScanResult(callbackType: Int, scanResult: ScanResult) {
- super.onScanResult(callbackType, scanResult)
- //Checks if the device has been found already
- if (!scanResultDictionary.containsKey(getPublicKey(scanResult))) {
- Timber.d("Found ${scanResult.device.address} at ${LocalDateTime.now()}")
- scanResultDictionary[getPublicKey(scanResult)] =
- DiscoveredDevice(scanResult, LocalDateTime.now())
- }
- }
-
- override fun onScanFailed(errorCode: Int) {
- super.onScanFailed(errorCode)
- Timber.e("Bluetooth scan failed $errorCode")
- if (BuildConfig.DEBUG) {
- notificationService.sendBLEErrorNotification()
- }
- }
- }
-
- private val locationRequester: LocationRequester = object : LocationRequester() {
- override fun receivedAccurateLocationUpdate(location: Location) {
- val started = locationFetchStarted ?: System.currentTimeMillis()
- Timber.d("Got location in ${(System.currentTimeMillis()-started)/1000}s")
- this@ScanBluetoothWorker.location = location
- this@ScanBluetoothWorker.locationRetrievedCallback?.let { it() }
- }
- }
-
- private fun getScanMode(): Int {
- val useLowPower = SharedPrefs.useLowPowerBLEScan
- return if (useLowPower) {
- ScanSettings.SCAN_MODE_LOW_POWER
- } else {
- ScanSettings.SCAN_MODE_LOW_LATENCY
- }
- }
-
- private fun getScanDuration(): Long {
- val useLowPower = SharedPrefs.useLowPowerBLEScan
- return if (useLowPower) {
- 30_000L
- } else {
- 20_000L
- }
- }
-
- private suspend fun waitForRequestedLocation(): Boolean {
- if (location != null || !SharedPrefs.useLocationInTrackingDetection) {
- //Location already there. Just return
- return true
- }
-
- return suspendCoroutine { cont ->
- var coroutineFinished = false
-
- val handler = Handler(Looper.getMainLooper())
- val runnable = Runnable {
- if (!coroutineFinished) {
- coroutineFinished = true
- locationRetrievedCallback = null
- Timber.d("Could not get location update in time.")
- cont.resume(false)
- }
- }
-
- locationRetrievedCallback = {
- if (!coroutineFinished) {
- handler.removeCallbacks(runnable)
- coroutineFinished = true
- cont.resume(true)
- }
- }
-
- // Fallback if no location is fetched in time
- val maximumLocationDurationMillis = LOCATION_UPDATE_MAX_TIME_MS
- handler.postDelayed(runnable, maximumLocationDurationMillis)
- }
- }
-
- class DiscoveredDevice(var scanResult: ScanResult, var discoveryDate: LocalDateTime)
-
- companion object {
- const val MAX_DISTANCE_UNTIL_NEW_LOCATION: Float = 150f // in meters
- const val TIME_BETWEEN_BEACONS: Long = 15 // 15 minutes until the same beacon gets saved again in the db
- const val LOCATION_UPDATE_MAX_TIME_MS: Long = 122_000L // Wait maximum 122s to get a location update
-
- suspend fun insertScanResult(
- scanResult: ScanResult,
- latitude: Double?,
- longitude: Double?,
- accuracy: Float?,
- discoveryDate: LocalDateTime,
- ): Pair {
- val deviceSaved = saveDevice(scanResult, discoveryDate) ?: return Pair(null, null) // return when device does not qualify to be saved
-
- // set locationId to null if gps location could not be retrieved
- val locId: Int? = saveLocation(latitude, longitude, discoveryDate, accuracy)?.locationId
-
- val beaconSaved = saveBeacon(scanResult, discoveryDate, locId) ?: return Pair(null, null)
-
- return Pair(deviceSaved, beaconSaved)
- }
-
- private suspend fun saveBeacon(
- scanResult: ScanResult,
- discoveryDate: LocalDateTime,
- locId: Int?
- ): Beacon? {
- val beaconRepository = ATTrackingDetectionApplication.getCurrentApp()?.beaconRepository ?: return null
- val uuids = scanResult.scanRecord?.serviceUuids?.map { it.toString() }?.toList()
- val uniqueIdentifier = getPublicKey(scanResult)
-
- val connectionState: ConnectionState = BaseDevice.getConnectionState(scanResult)
- val connectionStateString = Utility.connectionStateToString(connectionState)
-
- var beacon: Beacon? = null
- val beacons = beaconRepository.getDeviceBeaconsSince(
- deviceAddress = uniqueIdentifier,
- since = discoveryDate.minusMinutes(TIME_BETWEEN_BEACONS)
- ) // sorted by newest first
-
- if (beacons.isEmpty()) {
- Timber.d("Add new Beacon to the database!")
- beacon = if (BuildConfig.DEBUG) {
- // Save the manufacturer data to the beacon
- Beacon(
- discoveryDate, scanResult.rssi, getPublicKey(scanResult), locId,
- scanResult.scanRecord?.bytes, uuids, connectionStateString
- )
- } else {
- Beacon(
- discoveryDate, scanResult.rssi, getPublicKey(scanResult), locId,
- null, uuids, connectionStateString
- )
- }
- beaconRepository.insert(beacon)
- } else if (beacons[0].locationId == null && locId != null && locId != 0){
- // Update beacon within the last TIME_BETWEEN_BEACONS minutes with location
- Timber.d("Beacon already in the database... Adding Location")
- beacon = beacons[0]
- beacon.locationId = locId
- if (beacon.connectionState == "UNKNOWN" && connectionState != ConnectionState.UNKNOWN) {
- beacon.connectionState = connectionStateString
- }
- beaconRepository.update(beacon)
- }
-
- Timber.d("Beacon: $beacon")
-
- return beacon
- }
-
- private suspend fun saveDevice(
- scanResult: ScanResult,
- discoveryDate: LocalDateTime
- ): BaseDevice? {
- val deviceRepository = ATTrackingDetectionApplication.getCurrentApp()?.deviceRepository ?: return null
-
- val deviceAddress = getPublicKey(scanResult)
-
- // Checks if Device already exists in device database
- var device = deviceRepository.getDevice(deviceAddress)
- if (device == null) {
- // Do not Save Samsung Devices
- device = BaseDevice(scanResult)
-
- // Check if ConnectionState qualifies Device to be saved
- // Only Save when Device is offline long enough
- if (BaseDevice.getConnectionState(scanResult) !in DeviceManager.savedConnectionStates) {
- Timber.d("Device not in a saved connection state... Skipping!")
- return null
- }
-
- if (BaseDevice.getConnectionState(scanResult) !in DeviceManager.unsafeConnectionState) {
- Timber.d("Device is safe and will be hidden to the user!")
- device.safeTracker = true
- }
-
- Timber.d("Add new Device to the database!")
- deviceRepository.insert(device)
- } else {
- Timber.d("Device already in the database... Updating the last seen date!")
- device.lastSeen = discoveryDate
- deviceRepository.update(device)
- }
-
- Timber.d("Device: $device")
- return device
- }
-
- private suspend fun saveLocation(
- latitude: Double?,
- longitude: Double?,
- discoveryDate: LocalDateTime,
- accuracy: Float?
- ): LocationModel? {
- val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return null
-
- // set location to null if gps location could not be retrieved
- var location: LocationModel? = null
-
- if (latitude != null && longitude != null) {
- // Get closest location from database
- location = locationRepository.closestLocation(latitude, longitude)
-
- var distanceBetweenLocations: Float = Float.MAX_VALUE
-
- if (location != null) {
- val locationA = getLocation(latitude, longitude)
- val locationB = getLocation(location.latitude, location.longitude)
- distanceBetweenLocations = locationA.distanceTo(locationB)
- }
-
- if (location == null || distanceBetweenLocations > MAX_DISTANCE_UNTIL_NEW_LOCATION) {
- // Create new location entry
- Timber.d("Add new Location to the database!")
- location = LocationModel(discoveryDate, longitude, latitude, accuracy)
- locationRepository.insert(location)
- } else {
- // If location is within the set limit, just use that location and update lastSeen
- Timber.d("Location already in the database... Updating the last seen date!")
- location.lastSeen = discoveryDate
- locationRepository.update(location)
-
- }
-
- Timber.d("Location: $location")
- }
- return location
- }
- }
}
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 5aaff441..cd270e84 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
@@ -13,7 +13,7 @@ import dagger.hilt.components.SingletonComponent
import de.seemoo.at_tracking_detection.database.AppDatabase
import de.seemoo.at_tracking_detection.database.daos.*
import de.seemoo.at_tracking_detection.database.repository.*
-import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker.Companion.MAX_DISTANCE_UNTIL_NEW_LOCATION
+import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner
import de.seemoo.at_tracking_detection.detection.TrackingDetectorWorker.Companion.getLocation
import timber.log.Timber
import javax.inject.Singleton
@@ -80,7 +80,7 @@ object DatabaseModule {
val locationA = getLocation(latitude, longitude)
val locationB = getLocation(closestLatitude, closestLongitude)
val distanceBetweenLocations = locationA.distanceTo(locationB)
- if (distanceBetweenLocations > MAX_DISTANCE_UNTIL_NEW_LOCATION){
+ if (distanceBetweenLocations > BackgroundBluetoothScanner.MAX_DISTANCE_UNTIL_NEW_LOCATION){
// println("Insert New, because far enough away")
insertNewLocation = true
} else {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt
index d9a08f70..e8e1ff59 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt
@@ -17,6 +17,7 @@ import de.seemoo.at_tracking_detection.BuildConfig
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.util.SharedPrefs
import de.seemoo.at_tracking_detection.util.ble.BLEScanner
+import de.seemoo.at_tracking_detection.worker.BackgroundWorkScheduler
import org.osmdroid.config.Configuration
import timber.log.Timber
import java.io.File
@@ -29,6 +30,9 @@ class MainActivity : AppCompatActivity() {
@Inject
lateinit var sharedPreferences: SharedPreferences
+ @Inject
+ lateinit var backgroundWorkScheduler: BackgroundWorkScheduler
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -82,10 +86,16 @@ class MainActivity : AppCompatActivity() {
}
}
+ override fun onStart() {
+ super.onStart()
+ }
+
override fun onResume() {
super.onResume()
Timber.d("MainActivity onResume called")
BLEScanner.startBluetoothScan(this.applicationContext)
+
+ Timber.d("Scheduling an immediate background scan onResume of MainActivity")
}
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 fde8d0ea..31ed43f1 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
@@ -17,9 +17,9 @@ import de.seemoo.at_tracking_detection.database.models.device.DeviceManager.getD
import de.seemoo.at_tracking_detection.database.models.device.DeviceType.Companion.getAllowedDeviceTypesFromSettings
import de.seemoo.at_tracking_detection.database.repository.BeaconRepository
import de.seemoo.at_tracking_detection.database.repository.ScanRepository
+import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner
+import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner.TIME_BETWEEN_BEACONS
import de.seemoo.at_tracking_detection.detection.LocationProvider
-import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker
-import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker.Companion.TIME_BETWEEN_BEACONS
//import de.seemoo.at_tracking_detection.util.Utility
import de.seemoo.at_tracking_detection.util.ble.BLEScanner
import kotlinx.coroutines.MainScope
@@ -79,7 +79,7 @@ class ScanViewModel @Inject constructor(
Timber.d("Got location $location in ScanViewModel")
MainScope().async {
- ScanBluetoothWorker.insertScanResult(
+ BackgroundBluetoothScanner.insertScanResult(
scanResult = scanResult,
latitude = location?.latitude,
longitude = location?.longitude,
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/BLEScanCallback.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/BLEScanCallback.kt
index d91d4cbb..cbe17aaa 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/BLEScanCallback.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/BLEScanCallback.kt
@@ -39,8 +39,13 @@ object BLEScanCallback {
override fun onScanFailed(errorCode: Int) {
super.onScanFailed(errorCode)
- Timber.e("BLE Scan failed. $errorCode")
- scanCallback?.get()?.onScanFailed(errorCode)
+ if (errorCode != 1 ) {
+ Timber.e("BLE Scan failed. $errorCode")
+ scanCallback?.get()?.onScanFailed(errorCode)
+ }else {
+ // errorCode = 1 means that scan is already running
+ Timber.w("BLE Scan already running.")
+ }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkBuilder.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkBuilder.kt
index 7a6fa0dc..df037d0d 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkBuilder.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkBuilder.kt
@@ -20,6 +20,11 @@ class BackgroundWorkBuilder @Inject constructor() {
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkerConstants.KIND_DELAY, TimeUnit.SECONDS)
.build()
+ fun buildImmediateScanWorker(): OneTimeWorkRequest =
+ OneTimeWorkRequestBuilder().addTag(WorkerConstants.SCAN_IMMEDIATELY)
+ .setBackoffCriteria(BackoffPolicy.LINEAR, WorkerConstants.KIND_DELAY, TimeUnit.MINUTES)
+ .build()
+
fun buildSendStatisticsWorker(): PeriodicWorkRequest =
PeriodicWorkRequestBuilder(
WorkerConstants.MIN_HOURS_TO_NEXT_SEND_STATISTICS, TimeUnit.HOURS
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
index fe9a35b2..a00d4369 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
@@ -24,6 +24,13 @@ class BackgroundWorkScheduler @Inject constructor(
) {
fun launch() {
+ // We now scan with Alarms instead of periodic workers, since they have proven to be unreliable
+ scheduleScanWithAlarm()
+ // We cancel all periodic scans
+ workManager.cancelUniqueWork(WorkerConstants.PERIODIC_SCAN_WORKER)
+ }
+
+ fun legacyLaunch() {
Timber.d("Work scheduler started!")
workManager.enqueueUniquePeriodicWork(
WorkerConstants.PERIODIC_SCAN_WORKER,
@@ -35,6 +42,13 @@ class BackgroundWorkScheduler @Inject constructor(
}
}
+ fun scheduleImmediateBackgroundScan() {
+ Timber.d("Scheduling Immediate Background Scan Worker ")
+ workManager.enqueueUniqueWork(WorkerConstants.SCAN_IMMEDIATELY,
+ ExistingWorkPolicy.APPEND_OR_REPLACE,
+ backgroundWorkBuilder.buildImmediateScanWorker())
+ }
+
fun getState(uniqueWorkName: String): LiveData =
workManager.getWorkInfosByTagLiveData(uniqueWorkName).map { it.lastOrNull()?.state }
@@ -105,5 +119,40 @@ class BackgroundWorkScheduler @Inject constructor(
alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
Timber.d("Scheduled an alarm to reschedule the scan at $alarmDate")
}
+
+ fun scheduleScanWithAlarm() {
+ //Run in 15 min
+ val timeInMillisUntilNotification: Long = if (BuildConfig.DEBUG) {
+ 15 * 60 * 1000
+ } else {
+ 15 * 60 * 1000
+ }
+
+ val alarmDate = LocalDateTime.now().plus(timeInMillisUntilNotification, ChronoUnit.MILLIS)
+ val alarmTime = System.currentTimeMillis() + timeInMillisUntilNotification
+
+ val intent = Intent(ATTrackingDetectionApplication.getAppContext(), ScheduleWorkersReceiver::class.java)
+ intent.action = "AlarmManagerWakeUp_Perform_BackgroundScan"
+
+ val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
+ PendingIntent.getBroadcast(ATTrackingDetectionApplication.getAppContext(), -103,intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
+ }else {
+ PendingIntent.getBroadcast(ATTrackingDetectionApplication.getAppContext(), -103, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ }
+
+ val alarmManager = ATTrackingDetectionApplication.getAppContext().getSystemService(
+ Context.ALARM_SERVICE) as AlarmManager
+
+ // We use exact Alarms since we want regular background scans to happen.
+
+ try {
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ }catch (exception: SecurityException) {
+ // Alarm could not be scheduled because user disallowed battery exception
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ }
+
+ Timber.d("Scheduled an alarm to start a scan at $alarmDate")
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/ScheduleWorkersReceiver.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/ScheduleWorkersReceiver.kt
index 2a2db16f..c30b5a4f 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/ScheduleWorkersReceiver.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/ScheduleWorkersReceiver.kt
@@ -7,24 +7,50 @@ import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
+import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner
import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker
import de.seemoo.at_tracking_detection.util.SharedPrefs
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
-class ScheduleWorkersReceiver : BroadcastReceiver() {
+class ScheduleWorkersReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Timber.d("Broadcast received ${intent?.action}")
- val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()?.backgroundWorkScheduler
- //Enqueue the scan task
- backgroundWorkScheduler?.launch()
- if (SharedPrefs.shareData) {
- backgroundWorkScheduler?.scheduleShareData()
- }
- BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
- Timber.d("Scheduled background work")
- ATTrackingDetectionApplication.getCurrentApp()?.notificationService?.scheduleSurveyNotification(false)
+ if (intent?.action == "AlarmManagerWakeUp_Schedule_BackgroundScan") {
+ // The app has been launched because no scan was performed since two hours
+ val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()?.backgroundWorkScheduler
+ //Schedule the periodic scan worker which runs every 15min
+ backgroundWorkScheduler?.launch()
+ if (SharedPrefs.shareData) {
+ backgroundWorkScheduler?.scheduleShareData()
+ }
+ BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
+ }else {
+ // action = AlarmManagerWakeUp_Perform_BackgroundScan
+ // The app has been launched to perform another scan
+ BackgroundWorkScheduler.scheduleScanWithAlarm()
+ val app = ATTrackingDetectionApplication?.getCurrentApp()
+ if (app != null) {
+
+ @OptIn(DelicateCoroutinesApi::class)
+ GlobalScope.launch {
+ Timber.d("Running scan launched from Alert")
+ BackgroundBluetoothScanner.scanInBackground(startedFrom = "ScheduleWorkersReceiver")
+ }
+
+ }else {
+ Timber.d("Could not find required dependencies")
+ }
+ }
}
@@ -51,11 +77,21 @@ class ScheduleWorkersReceiver : BroadcastReceiver() {
WorkManager.getInstance(context).enqueue(workRequestObserveTracker)
}
-// fun cancelWorker(context: Context, deviceAddress: String) {
-// val workManager = WorkManager.getInstance(context)
-// val workRequests = workManager.getWorkInfosByTag(deviceAddress)
-//
-//
-// }
+ }
+}
+
+// From: https://stackoverflow.com/questions/74111692/run-coroutine-functions-on-broadcast-receiver
+fun BroadcastReceiver.goAsync(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> Unit
+) {
+ val pendingResult = goAsync()
+ @OptIn(DelicateCoroutinesApi::class) // Must run globally; there's no teardown callback.
+ GlobalScope.launch(context) {
+ try {
+ block()
+ } finally {
+ pendingResult.finish()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/WorkerConstants.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/WorkerConstants.kt
index 8578c0d5..dc148782 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/WorkerConstants.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/WorkerConstants.kt
@@ -4,6 +4,7 @@ object WorkerConstants {
const val MIN_MINUTES_TO_NEXT_BT_SCAN = 15L
const val MIN_HOURS_TO_NEXT_SEND_STATISTICS = 4L
const val PERIODIC_SCAN_WORKER = "PeriodicScanWorker"
+ const val SCAN_IMMEDIATELY = "PeriodicScanWorker_NOW"
const val PERIODIC_SEND_STATISTICS_WORKER = "PeriodicSendStatisticsWorker"
const val ONETIME_SEND_STATISTICS_WORKER = "OneTimeSendStatisticsWorker"
const val TRACKING_DETECTION_WORKER = "TrackingDetectionWorker"
From 9df5356dff09af119c825e582160f3a08718b32a Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Fri, 22 Mar 2024 13:39:15 +0100
Subject: [PATCH 106/154] Fixing map marker display issue: - On the
TrackingFragment the map now shows the markers as expected.
---
.../ui/dashboard/DeviceMapFragment.kt | 20 ++++--
.../ui/tracking/TrackingFragment.kt | 62 ++++++++++++-------
.../at_tracking_detection/util/Utility.kt | 3 -
3 files changed, 53 insertions(+), 32 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
index 7c4e7226..fac48703 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
@@ -48,16 +48,23 @@ class DeviceMapFragment : Fragment() {
return binding.root
}
+ var map: MapView? = null
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ViewCompat.setTranslationZ(view, 100f)
- val map: MapView = view.findViewById(R.id.map)
+ map = view.findViewById(R.id.map)
Utility.checkAndRequestPermission(Manifest.permission.ACCESS_FINE_LOCATION)
viewModel.isMapLoading.postValue(true)
- Utility.enableMyLocationOverlay(map)
+
+ }
+
+ override fun onResume() {
+ super.onResume()
val deviceAddress = this.deviceAddress
+
if (!deviceAddress.isNullOrEmpty()) {
viewModel.markerLocations.observe(viewLifecycleOwner) {
lifecycleScope.launch {
@@ -71,8 +78,10 @@ class DeviceMapFragment : Fragment() {
locationList.add(location)
}
}
+ map?.let {
+ Utility.setGeoPointsFromListOfLocations(locationList.toList(), it, true)
+ }
- Utility.setGeoPointsFromListOfLocations(locationList.toList(), map, true)
}.invokeOnCompletion {
viewModel.isMapLoading.postValue(false)
}
@@ -91,8 +100,9 @@ class DeviceMapFragment : Fragment() {
locationList.add(location)
}
}
-
- Utility.setGeoPointsFromListOfLocations(locationList.toList(), map)
+ map?.let {
+ Utility.setGeoPointsFromListOfLocations(locationList.toList(), it)
+ }
}.invokeOnCompletion {
viewModel.isMapLoading.postValue(false)
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 1ce73c05..326d55f8 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -49,6 +49,8 @@ class TrackingFragment : Fragment() {
private val safeArgs: TrackingFragmentArgs by navArgs()
+ private var map: MapView? = null
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@@ -76,6 +78,11 @@ class TrackingFragment : Fragment() {
return binding.root
}
+ override fun onStart() {
+ super.onStart()
+ showMarkersOnMap()
+ }
+
@SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onResume() {
super.onResume()
@@ -125,7 +132,7 @@ class TrackingFragment : Fragment() {
val playSoundCard: CardView = view.findViewById(R.id.tracking_play_sound)
val trackingDetailButton: CardView = view.findViewById(R.id.tracking_detail_scan)
val observeTrackerButton: CardView = view.findViewById(R.id.tracking_observation)
- val map: MapView = view.findViewById(R.id.map)
+ map = view.findViewById(R.id.map)
val includedLayout: View = view.findViewById(R.id.manufacturer_website)
val identifierExplanation: TextView = view.findViewById(R.id.identifier_explanation)
@@ -174,29 +181,7 @@ class TrackingFragment : Fragment() {
}
}
- Utility.enableMyLocationOverlay(map)
-
- trackingViewModel.markerLocations.observe(viewLifecycleOwner) {
- lifecycleScope.launch {
- trackingViewModel.isMapLoading.postValue(true)
-
- val locationList = arrayListOf()
- val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository!!
-
- it.filter { it.locationId != null && it.locationId != 0 }
- .map {
- val location = locationRepository.getLocationWithId(it.locationId!!)
- if (location != null) {
- locationList.add(location)
- }
- }
-
- // This is the per Device View
- Utility.setGeoPointsFromListOfLocations(locationList.toList(), map, true)
- }.invokeOnCompletion {
- trackingViewModel.isMapLoading.postValue(false)
- }
- }
+// Utility.enableMyLocationOverlay(map)
trackingViewModel.soundPlaying.observe(viewLifecycleOwner) {
if (!it) {
@@ -296,4 +281,33 @@ class TrackingFragment : Fragment() {
trackingViewModel.connecting.postValue(false)
}
}
+
+ private fun showMarkersOnMap() {
+
+ trackingViewModel.markerLocations.observe(viewLifecycleOwner) {
+ lifecycleScope.launch {
+ trackingViewModel.isMapLoading.postValue(true)
+
+ val locationList = arrayListOf()
+ val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository!!
+
+ it.filter { it.locationId != null && it.locationId != 0 }
+ .map {
+ val location = locationRepository.getLocationWithId(it.locationId!!)
+ if (location != null) {
+ locationList.add(location)
+ }
+ }
+
+ // This is the per Device View
+ map?.let {
+ Utility.setGeoPointsFromListOfLocations(locationList.toList(), it, true)
+ it?.visibility = View.VISIBLE
+ }
+
+ }.invokeOnCompletion {
+ trackingViewModel.isMapLoading.postValue(false)
+ }
+ }
+ }
}
\ 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 51796b6a..fecb0fcb 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
@@ -114,9 +114,6 @@ object Utility {
locationList
.filter { it.locationId != 0 }
.forEach { location ->
- if (!map.isShown) {
- return@forEach
- }
val marker = Marker(map)
val geoPoint = GeoPoint(location.latitude, location.longitude)
From 3bd17215da7f8468fc8a9ae08f4791a498516b5a Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Fri, 22 Mar 2024 14:57:59 +0100
Subject: [PATCH 107/154] fix merge conflict
---
.../ui/dashboard/DeviceMapFragment.kt | 53 +---------------
.../ui/tracking/TrackingFragment.kt | 63 +------------------
.../at_tracking_detection/util/Utility.kt | 4 --
3 files changed, 3 insertions(+), 117 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
index ee49ee8a..0cf77a13 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DeviceMapFragment.kt
@@ -49,64 +49,15 @@ class DeviceMapFragment : Fragment() {
return binding.root
}
- var map: MapView? = null
-
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ViewCompat.setTranslationZ(view, 100f)
- map = view.findViewById(R.id.map)
+ val map: MapView = view.findViewById(R.id.map)
Utility.checkAndRequestPermission(Manifest.permission.ACCESS_FINE_LOCATION)
viewModel.isMapLoading.postValue(true)
+ Utility.enableMyLocationOverlay(map)
-
- }
-
- override fun onResume() {
- super.onResume()
- val deviceAddress = this.deviceAddress
-
- if (!deviceAddress.isNullOrEmpty()) {
- viewModel.markerLocations.observe(viewLifecycleOwner) {
- lifecycleScope.launch {
- val locationList = arrayListOf()
- val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return@launch
-
- it.filter { it.locationId != null && it.locationId != 0 }
- .map {
- val location = locationRepository.getLocationWithId(it.locationId!!)
- if (location != null) {
- locationList.add(location)
- }
- }
- map?.let {
- Utility.setGeoPointsFromListOfLocations(locationList.toList(), it, true)
- }
-
- }.invokeOnCompletion {
- viewModel.isMapLoading.postValue(false)
- }
- }
- } else {
- viewModel.allLocations.observe(viewLifecycleOwner) {
- lifecycleScope.launch {
- val locationList = arrayListOf()
- val locationRepository =
- ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return@launch
-
- it.filter { it.locationId != null && it.locationId != 0 }
- .map {
- val location = locationRepository.getLocationWithId(it.locationId!!)
- if (location != null) {
- locationList.add(location)
- }
- }
- map?.let {
- Utility.setGeoPointsFromListOfLocations(locationList.toList(), it)
- }
- }.invokeOnCompletion {
- viewModel.isMapLoading.postValue(false)
- }
lifecycleScope.launch {
val locationRepository = ATTrackingDetectionApplication.getCurrentApp().locationRepository
val relevantTrackingDate = RiskLevelEvaluator.relevantTrackingDateForRiskCalculation
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 7c5fa374..e2030378 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -80,11 +80,6 @@ class TrackingFragment : Fragment() {
return binding.root
}
- override fun onStart() {
- super.onStart()
- showMarkersOnMap()
- }
-
@SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onResume() {
super.onResume()
@@ -146,7 +141,7 @@ class TrackingFragment : Fragment() {
}
override fun onScroll(event: ScrollEvent?): Boolean {
- // Handle scroll event if needed
+ // Handle scroll event
return true
}
})
@@ -154,13 +149,6 @@ class TrackingFragment : Fragment() {
if (trackingViewModel.markerLocations.value != null && !trackingViewModel.isMapLoading.value!!) {
zoomToMarkers()
}
- val feedbackButton: CardView = view.findViewById(R.id.tracking_feedback)
- val playSoundCard: CardView = view.findViewById(R.id.tracking_play_sound)
- val trackingDetailButton: CardView = view.findViewById(R.id.tracking_detail_scan)
- val observeTrackerButton: CardView = view.findViewById(R.id.tracking_observation)
- map = view.findViewById(R.id.map)
- val includedLayout: View = view.findViewById(R.id.manufacturer_website)
- val identifierExplanation: TextView = view.findViewById(R.id.identifier_explanation)
trackingViewModel.deviceType.observe(viewLifecycleOwner) { deviceType ->
view.findViewById(R.id.identifier_explanation).text =
@@ -261,27 +249,9 @@ class TrackingFragment : Fragment() {
}
}
-// Utility.enableMyLocationOverlay(map)
-
- trackingViewModel.soundPlaying.observe(viewLifecycleOwner) {
- if (!it) {
- try {
- requireContext().unbindService(serviceConnection)
- } catch (e: IllegalArgumentException) {
- Timber.e("Tried to unbind an unbound service!")
- } finally {
- Timber.d("Service connection unregistered")
- }
- }
- }
-
- addInteractions(view)
- }
-
private fun addInteractions(view: View) {
val button = view.findViewById(R.id.open_map_button)
-
button.setOnClickListener {
val direction = TrackingFragmentDirections.actionTrackingFragmentToDeviceMapFragment(showAllDevices = false, deviceAddress = trackingViewModel.deviceAddress.value)
findNavController().navigate(direction)
@@ -292,8 +262,6 @@ class TrackingFragment : Fragment() {
val direction = TrackingFragmentDirections.actionTrackingFragmentToDeviceMapFragment(showAllDevices = false, deviceAddress = trackingViewModel.deviceAddress.value)
findNavController().navigate(direction)
}
-
-
}
private fun toggleSound() {
@@ -361,33 +329,4 @@ class TrackingFragment : Fragment() {
trackingViewModel.connecting.postValue(false)
}
}
-
- private fun showMarkersOnMap() {
-
- trackingViewModel.markerLocations.observe(viewLifecycleOwner) {
- lifecycleScope.launch {
- trackingViewModel.isMapLoading.postValue(true)
-
- val locationList = arrayListOf()
- val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository!!
-
- it.filter { it.locationId != null && it.locationId != 0 }
- .map {
- val location = locationRepository.getLocationWithId(it.locationId!!)
- if (location != null) {
- locationList.add(location)
- }
- }
-
- // This is the per Device View
- map?.let {
- Utility.setGeoPointsFromListOfLocations(locationList.toList(), it, true)
- it?.visibility = View.VISIBLE
- }
-
- }.invokeOnCompletion {
- trackingViewModel.isMapLoading.postValue(false)
- }
- }
- }
}
\ 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 d1844088..e55604ab 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
@@ -124,10 +124,6 @@ object Utility {
locationList
.filter { it.locationId != 0 }
.forEach { location ->
- if (!map.isShown) {
- return@forEach
- }
-
val marker = Marker(map)
val geoPoint = GeoPoint(location.latitude, location.longitude)
marker.position = geoPoint
From d5f5a296200753414ebedc49927d3b78c196567d Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Fri, 22 Mar 2024 15:32:25 +0100
Subject: [PATCH 108/154] add indices to Beacon and BaseDevice
---
.../15.json | 483 ++++++++++++++++++
.../database/AppDatabase.kt | 3 +-
.../database/models/Beacon.kt | 14 +-
.../database/models/device/BaseDevice.kt | 11 +-
4 files changed, 504 insertions(+), 7 deletions(-)
create mode 100644 app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/15.json
diff --git a/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/15.json b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/15.json
new file mode 100644
index 00000000..8cba519b
--- /dev/null
+++ b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/15.json
@@ -0,0 +1,483 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 15,
+ "identityHash": "8175e59910289659de2d3d985db686d4",
+ "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, `riskLevel` INTEGER NOT NULL DEFAULT 0, `lastCalculatedRiskDate` TEXT, `nextObservationNotification` TEXT, `currentObservationDuration` INTEGER, `safeTracker` INTEGER NOT NULL DEFAULT false)",
+ "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
+ },
+ {
+ "fieldPath": "riskLevel",
+ "columnName": "riskLevel",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "lastCalculatedRiskDate",
+ "columnName": "lastCalculatedRiskDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextObservationNotification",
+ "columnName": "nextObservationNotification",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "currentObservationDuration",
+ "columnName": "currentObservationDuration",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "safeTracker",
+ "columnName": "safeTracker",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "false"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "deviceId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_device_lastSeen",
+ "unique": false,
+ "columnNames": [
+ "lastSeen"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_device_lastSeen` ON `${TABLE_NAME}` (`lastSeen`)"
+ },
+ {
+ "name": "index_device_address",
+ "unique": true,
+ "columnNames": [
+ "address"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_device_address` ON `${TABLE_NAME}` (`address`)"
+ },
+ {
+ "name": "index_device_notificationSent",
+ "unique": false,
+ "columnNames": [
+ "notificationSent"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_device_notificationSent` ON `${TABLE_NAME}` (`notificationSent`)"
+ },
+ {
+ "name": "index_device_deviceType",
+ "unique": false,
+ "columnNames": [
+ "deviceType"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_device_deviceType` ON `${TABLE_NAME}` (`deviceType`)"
+ },
+ {
+ "name": "index_device_lastSeen_deviceType",
+ "unique": false,
+ "columnNames": [
+ "lastSeen",
+ "deviceType"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_device_lastSeen_deviceType` ON `${TABLE_NAME}` (`lastSeen`, `deviceType`)"
+ }
+ ],
+ "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, `sensitivity` INTEGER NOT NULL DEFAULT 0)",
+ "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
+ },
+ {
+ "fieldPath": "sensitivity",
+ "columnName": "sensitivity",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "notificationId"
+ ]
+ },
+ "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, `locationId` INTEGER, `mfg` BLOB, `serviceUUIDs` TEXT, `connectionState` TEXT NOT NULL DEFAULT 'UNKNOWN')",
+ "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": "locationId",
+ "columnName": "locationId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "manufacturerData",
+ "columnName": "mfg",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serviceUUIDs",
+ "columnName": "serviceUUIDs",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "connectionState",
+ "columnName": "connectionState",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'UNKNOWN'"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "beaconId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_beacon_receivedAt",
+ "unique": false,
+ "columnNames": [
+ "receivedAt"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_beacon_receivedAt` ON `${TABLE_NAME}` (`receivedAt`)"
+ },
+ {
+ "name": "index_beacon_deviceAddress",
+ "unique": false,
+ "columnNames": [
+ "deviceAddress"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_beacon_deviceAddress` ON `${TABLE_NAME}` (`deviceAddress`)"
+ },
+ {
+ "name": "index_beacon_connectionState",
+ "unique": false,
+ "columnNames": [
+ "connectionState"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_beacon_connectionState` ON `${TABLE_NAME}` (`connectionState`)"
+ }
+ ],
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "feedbackId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "scan",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scanId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `endDate` TEXT, `noDevicesFound` INTEGER, `duration` INTEGER, `isManual` INTEGER NOT NULL, `scanMode` INTEGER NOT NULL, `startDate` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "scanId",
+ "columnName": "scanId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endDate",
+ "columnName": "endDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "noDevicesFound",
+ "columnName": "noDevicesFound",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "duration",
+ "columnName": "duration",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isManual",
+ "columnName": "isManual",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "scanMode",
+ "columnName": "scanMode",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startDate",
+ "columnName": "startDate",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "scanId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "location",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`locationId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `firstDiscovery` TEXT NOT NULL, `lastSeen` TEXT NOT NULL, `longitude` REAL NOT NULL, `latitude` REAL NOT NULL, `accuracy` REAL)",
+ "fields": [
+ {
+ "fieldPath": "locationId",
+ "columnName": "locationId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstDiscovery",
+ "columnName": "firstDiscovery",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastSeen",
+ "columnName": "lastSeen",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longitude",
+ "columnName": "longitude",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latitude",
+ "columnName": "latitude",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accuracy",
+ "columnName": "accuracy",
+ "affinity": "REAL",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "locationId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_location_latitude_longitude",
+ "unique": true,
+ "columnNames": [
+ "latitude",
+ "longitude"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_location_latitude_longitude` ON `${TABLE_NAME}` (`latitude`, `longitude`)"
+ }
+ ],
+ "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, '8175e59910289659de2d3d985db686d4')"
+ ]
+ }
+}
\ No newline at end of file
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 3e9fd398..0bdab6b3 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
@@ -8,7 +8,7 @@ import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.util.converter.DateTimeConverter
@Database(
- version = 14,
+ version = 15,
entities = [
BaseDevice::class,
Notification::class,
@@ -28,6 +28,7 @@ import de.seemoo.at_tracking_detection.util.converter.DateTimeConverter
AutoMigration(from=11, to=12),
AutoMigration(from=12, to=13),
AutoMigration(from=13, to=14),
+ AutoMigration(from=14, to=15)
],
exportSchema = true
)
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 2959bc6f..30c02521 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
@@ -1,16 +1,20 @@
package de.seemoo.at_tracking_detection.database.models
-import androidx.room.ColumnInfo
-import androidx.room.Entity
-import androidx.room.PrimaryKey
-import androidx.room.TypeConverters
+import androidx.room.*
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")
+@Entity(
+ tableName = "beacon",
+ indices = [
+ Index(value = ["receivedAt"]),
+ Index(value = ["deviceAddress"]),
+ Index(value = ["connectionState"])
+ ]
+)
@TypeConverters(DateTimeConverter::class, Converters::class)
data class Beacon(
@PrimaryKey(autoGenerate = true) val beaconId: Int,
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 31315925..39e20939 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
@@ -12,7 +12,16 @@ import java.time.format.FormatStyle
import java.util.*
import kotlin.experimental.and
-@Entity(tableName = "device", indices = [Index(value = ["address"], unique = true)])
+@Entity(
+ tableName = "device",
+ indices = [
+ Index(value = ["lastSeen"]),
+ Index(value = ["address"], unique = true),
+ Index(value = ["notificationSent"]),
+ Index(value = ["deviceType"]),
+ Index(value = ["lastSeen", "deviceType"])
+ ]
+)
@TypeConverters(DateTimeConverter::class)
data class BaseDevice(
@PrimaryKey(autoGenerate = true) var deviceId: Int,
From 1ef909fa04c4ef0762f237f722663acd96d22365 Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Fri, 22 Mar 2024 18:05:52 +0100
Subject: [PATCH 109/154] Checking permission for setExact Alarm Using a wake
lock on the BackgroundBluetoothScanner Replacing Beacon count with times seen
---
app/src/main/AndroidManifest.xml | 3 +
.../detection/BackgroundBluetoothScanner.kt | 431 ++++++++++++++++++
.../at_tracking_detection/ui/MainActivity.kt | 6 +
.../at_tracking_detection/util/Utility.kt | 9 +
.../worker/BackgroundWorkScheduler.kt | 69 ++-
app/src/main/res/layout/item_device.xml | 6 +-
app/src/main/res/values-de/strings.xml | 1 +
app/src/main/res/values/strings.xml | 3 +-
8 files changed, 518 insertions(+), 10 deletions(-)
create mode 100644 app/src/main/java/de/seemoo/at_tracking_detection/detection/BackgroundBluetoothScanner.kt
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5c300e76..f3b45eb8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -33,6 +33,9 @@
+
+
+
= ConcurrentHashMap()
+
+ private var applicationContext: Context = ATTrackingDetectionApplication.getAppContext()
+
+ var location: android.location.Location? = null
+ set(value) {
+ field = value
+ if (value != null) {
+ locationRetrievedCallback?.let { it() }
+ }
+ }
+
+ private var locationRetrievedCallback: (() -> Unit)? = null
+
+ private var locationFetchStarted: Long? = null
+
+ val backgroundWorkScheduler: BackgroundWorkScheduler?
+ get() {
+ return ATTrackingDetectionApplication.getCurrentApp()?.backgroundWorkScheduler
+ }
+
+ val notificationService: NotificationService?
+ get() {
+ return ATTrackingDetectionApplication.getCurrentApp()?.notificationService
+ }
+ val locationProvider: LocationProvider?
+ get() {
+ return ATTrackingDetectionApplication.getCurrentApp()?.locationProvider
+ }
+
+ val scanRepository: ScanRepository?
+ get() {
+ return ATTrackingDetectionApplication.getCurrentApp()?.scanRepository
+ }
+
+ var isScanning = false
+ class BackgroundScanResults(var duration: Long, var scanMode: Int, var numberDevicesFound: Int, var failed: Boolean)
+ suspend fun scanInBackground(startedFrom: String): BackgroundScanResults {
+ if (isScanning) {
+ Timber.w("BLE background scan already running")
+ return BackgroundScanResults(0, 0, 0, true)
+ }
+
+ Timber.d("Starting BackgroundBluetoothScanner from $startedFrom")
+ val scanMode = getScanMode()
+ val scanId = scanRepository?.insert(Scan(startDate = LocalDateTime.now(), isManual = false, scanMode = scanMode))
+
+
+ if (!Utility.checkBluetoothPermission()) {
+ Timber.d("Permission to perform bluetooth scan missing")
+ return BackgroundScanResults(0, 0, 0, true)
+ }
+ try {
+ val bluetoothManager =
+ applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
+ bluetoothAdapter = bluetoothManager.adapter
+ } catch (e: Throwable) {
+ Timber.e("BluetoothAdapter not found!")
+ return BackgroundScanResults(0, 0, 0, true)
+ }
+
+ scanResultDictionary = ConcurrentHashMap()
+ isScanning = true
+ location = null
+
+ // Set a wake lock to keep the CPU running while we complete the scanning
+ val wakeLock: PowerManager.WakeLock =
+ (applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).run {
+ newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
+ acquire(5*60*1000L /*5 minutes*/)
+ }
+ }
+
+ val useLocation = SharedPrefs.useLocationInTrackingDetection
+ if (useLocation) {
+ // Returns the last known location if this matches our requirements or starts new location updates
+ locationFetchStarted = System.currentTimeMillis()
+ location = locationProvider?.lastKnownOrRequestLocationUpdates(locationRequester = locationRequester, timeoutMillis = BackgroundBluetoothScanner.LOCATION_UPDATE_MAX_TIME_MS - 2000L)
+ }
+
+ //Starting BLE Scan
+ Timber.d("Start Scanning for bluetooth le devices...")
+ val scanSettings =
+ ScanSettings.Builder().setScanMode(scanMode).build()
+
+ SharedPrefs.isScanningInBackground = true
+ BLEScanCallback.startScanning(bluetoothAdapter.bluetoothLeScanner, DeviceManager.scanFilter, scanSettings, leScanCallback)
+
+ val scanDuration: Long = getScanDuration()
+ delay(scanDuration)
+ BLEScanCallback.stopScanning(bluetoothAdapter.bluetoothLeScanner)
+ Timber.d("Scanning for bluetooth le devices stopped!. Discovered ${scanResultDictionary.size} devices")
+
+ //Waiting for updated location to come in
+ Timber.d("Waiting for location update")
+ val fetchedLocation = waitForRequestedLocation()
+ Timber.d("Fetched location? $fetchedLocation")
+ if (location == null) {
+ // Get the last location no matter if the requirements match or not
+ location = locationProvider?.getLastLocation(checkRequirements = false)
+ }
+
+ val validDeviceTypes = DeviceType.getAllowedDeviceTypesFromSettings()
+
+ //Adding all scan results to the database after the scan has finished
+ scanResultDictionary.forEach { (_, discoveredDevice) ->
+ val deviceType = DeviceManager.getDeviceType(discoveredDevice.scanResult)
+
+ if (deviceType in validDeviceTypes) {
+ BackgroundBluetoothScanner.insertScanResult(
+ discoveredDevice.scanResult,
+ location?.latitude,
+ location?.longitude,
+ location?.accuracy,
+ discoveredDevice.discoveryDate,
+ )
+ }
+ }
+
+ SharedPrefs.lastScanDate = LocalDateTime.now()
+ SharedPrefs.isScanningInBackground = false
+ if (scanId != null) {
+ val scan = scanRepository?.scanWithId(scanId.toInt())
+ if (scan != null) {
+ scan.endDate = LocalDateTime.now()
+ scan.duration = scanDuration.toInt() / 1000
+ scan.noDevicesFound = scanResultDictionary.size
+ scanRepository?.update(scan)
+ }
+ }
+
+ Timber.d("Scheduling tracking detector worker")
+ backgroundWorkScheduler?.scheduleTrackingDetector()
+ BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
+
+ isScanning = true
+ // Release the wake lock when we are done
+ wakeLock.release()
+
+ Timber.d("Finished Background Scan")
+ return BackgroundScanResults(
+ duration = scanDuration,
+ scanMode = scanMode,
+ numberDevicesFound = scanResultDictionary.size,
+ failed = false
+ )
+ }
+
+ private val leScanCallback: ScanCallback = object : ScanCallback() {
+ override fun onScanResult(callbackType: Int, scanResult: ScanResult) {
+ super.onScanResult(callbackType, scanResult)
+ //Checks if the device has been found already
+ if (!scanResultDictionary.containsKey(BaseDevice.getPublicKey(scanResult))) {
+ Timber.d("Found ${scanResult.device.address} at ${LocalDateTime.now()}")
+ scanResultDictionary[BaseDevice.getPublicKey(scanResult)] =
+ DiscoveredDevice(scanResult, LocalDateTime.now())
+ }
+ }
+
+ override fun onScanFailed(errorCode: Int) {
+ super.onScanFailed(errorCode)
+ Timber.e("Bluetooth scan failed $errorCode")
+ if (BuildConfig.DEBUG) {
+ notificationService?.sendBLEErrorNotification()
+ }
+ }
+ }
+
+ private val locationRequester: LocationRequester = object : LocationRequester() {
+ override fun receivedAccurateLocationUpdate(location: android.location.Location) {
+ val started = locationFetchStarted ?: System.currentTimeMillis()
+ Timber.d("Got location in ${(System.currentTimeMillis()-started)/1000}s")
+ this@BackgroundBluetoothScanner.location = location
+ this@BackgroundBluetoothScanner.locationRetrievedCallback?.let { it() }
+ }
+ }
+
+ private fun getScanMode(): Int {
+ val useLowPower = SharedPrefs.useLowPowerBLEScan
+ return if (useLowPower) {
+ ScanSettings.SCAN_MODE_LOW_POWER
+ } else {
+ ScanSettings.SCAN_MODE_LOW_LATENCY
+ }
+ }
+
+ private fun getScanDuration(): Long {
+ val useLowPower = SharedPrefs.useLowPowerBLEScan
+ return if (useLowPower) {
+ 30_000L
+ } else {
+ 20_000L
+ }
+ }
+
+ private suspend fun waitForRequestedLocation(): Boolean {
+ if (location != null || !SharedPrefs.useLocationInTrackingDetection) {
+ //Location already there. Just return
+ return true
+ }
+
+ return suspendCoroutine { cont ->
+ var coroutineFinished = false
+
+ val handler = Handler(Looper.getMainLooper())
+ val runnable = Runnable {
+ if (!coroutineFinished) {
+ coroutineFinished = true
+ locationRetrievedCallback = null
+ Timber.d("Could not get location update in time.")
+ cont.resume(false)
+ }
+ }
+
+ locationRetrievedCallback = {
+ if (!coroutineFinished) {
+ handler.removeCallbacks(runnable)
+ coroutineFinished = true
+ cont.resume(true)
+ }
+ }
+
+ // Fallback if no location is fetched in time
+ val maximumLocationDurationMillis = BackgroundBluetoothScanner.LOCATION_UPDATE_MAX_TIME_MS
+ handler.postDelayed(runnable, maximumLocationDurationMillis)
+ }
+ }
+
+ class DiscoveredDevice(var scanResult: ScanResult, var discoveryDate: LocalDateTime)
+
+ const val MAX_DISTANCE_UNTIL_NEW_LOCATION: Float = 150f // in meters
+ const val TIME_BETWEEN_BEACONS: Long =
+ 15 // 15 minutes until the same beacon gets saved again in the db
+ const val LOCATION_UPDATE_MAX_TIME_MS: Long =
+ 122_000L // Wait maximum 122s to get a location update
+
+ suspend fun insertScanResult(
+ scanResult: ScanResult,
+ latitude: Double?,
+ longitude: Double?,
+ accuracy: Float?,
+ discoveryDate: LocalDateTime,
+ ): Pair {
+ val deviceSaved = saveDevice(scanResult, discoveryDate) ?: return Pair(
+ null,
+ null
+ ) // return when device does not qualify to be saved
+
+ // set locationId to null if gps location could not be retrieved
+ val locId: Int? = saveLocation(latitude, longitude, discoveryDate, accuracy)?.locationId
+
+ val beaconSaved =
+ saveBeacon(scanResult, discoveryDate, locId) ?: return Pair(null, null)
+
+ return Pair(deviceSaved, beaconSaved)
+ }
+
+ private suspend fun saveBeacon(
+ scanResult: ScanResult,
+ discoveryDate: LocalDateTime,
+ locId: Int?
+ ): Beacon? {
+ val beaconRepository =
+ ATTrackingDetectionApplication.getCurrentApp()?.beaconRepository ?: return null
+ val uuids = scanResult.scanRecord?.serviceUuids?.map { it.toString() }?.toList()
+ val uniqueIdentifier = BaseDevice.getPublicKey(scanResult)
+
+ val connectionState: ConnectionState = BaseDevice.getConnectionState(scanResult)
+ val connectionStateString = Utility.connectionStateToString(connectionState)
+
+ var beacon: Beacon? = null
+ val beacons = beaconRepository.getDeviceBeaconsSince(
+ deviceAddress = uniqueIdentifier,
+ since = discoveryDate.minusMinutes(TIME_BETWEEN_BEACONS)
+ ) // sorted by newest first
+
+ if (beacons.isEmpty()) {
+ Timber.d("Add new Beacon to the database!")
+ beacon = if (BuildConfig.DEBUG) {
+ // Save the manufacturer data to the beacon
+ Beacon(
+ discoveryDate, scanResult.rssi, BaseDevice.getPublicKey(scanResult), locId,
+ scanResult.scanRecord?.bytes, uuids, connectionStateString
+ )
+ } else {
+ Beacon(
+ discoveryDate, scanResult.rssi, BaseDevice.getPublicKey(scanResult), locId,
+ null, uuids, connectionStateString
+ )
+ }
+ beaconRepository.insert(beacon)
+ } else if (beacons[0].locationId == null && locId != null && locId != 0) {
+ // Update beacon within the last TIME_BETWEEN_BEACONS minutes with location
+ Timber.d("Beacon already in the database... Adding Location")
+ beacon = beacons[0]
+ beacon.locationId = locId
+ if (beacon.connectionState == "UNKNOWN" && connectionState != ConnectionState.UNKNOWN) {
+ beacon.connectionState = connectionStateString
+ }
+ beaconRepository.update(beacon)
+ }
+
+ Timber.d("Beacon: $beacon")
+
+ return beacon
+ }
+
+ private suspend fun saveDevice(
+ scanResult: ScanResult,
+ discoveryDate: LocalDateTime
+ ): BaseDevice? {
+ val deviceRepository =
+ ATTrackingDetectionApplication.getCurrentApp()?.deviceRepository ?: return null
+
+ val deviceAddress = BaseDevice.getPublicKey(scanResult)
+
+ // Checks if Device already exists in device database
+ var device = deviceRepository.getDevice(deviceAddress)
+ if (device == null) {
+ // Do not Save Samsung Devices
+ device = BaseDevice(scanResult)
+
+ // Check if ConnectionState qualifies Device to be saved
+ // Only Save when Device is offline long enough
+ if (BaseDevice.getConnectionState(scanResult) !in DeviceManager.savedConnectionStates) {
+ Timber.d("Device not in a saved connection state... Skipping!")
+ return null
+ }
+
+ if (BaseDevice.getConnectionState(scanResult) !in DeviceManager.unsafeConnectionState) {
+ Timber.d("Device is safe and will be hidden to the user!")
+ device.safeTracker = true
+ }
+
+ Timber.d("Add new Device to the database!")
+ deviceRepository.insert(device)
+ } else {
+ Timber.d("Device already in the database... Updating the last seen date!")
+ device.lastSeen = discoveryDate
+ deviceRepository.update(device)
+ }
+
+ Timber.d("Device: $device")
+ return device
+ }
+
+ private suspend fun saveLocation(
+ latitude: Double?,
+ longitude: Double?,
+ discoveryDate: LocalDateTime,
+ accuracy: Float?
+ ): Location? {
+ val locationRepository =
+ ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return null
+
+ // set location to null if gps location could not be retrieved
+ var location: Location? = null
+
+ if (latitude != null && longitude != null) {
+ // Get closest location from database
+ location = locationRepository.closestLocation(latitude, longitude)
+
+ var distanceBetweenLocations: Float = Float.MAX_VALUE
+
+ if (location != null) {
+ val locationA = TrackingDetectorWorker.getLocation(latitude, longitude)
+ val locationB =
+ TrackingDetectorWorker.getLocation(location.latitude, location.longitude)
+ distanceBetweenLocations = locationA.distanceTo(locationB)
+ }
+
+ if (location == null || distanceBetweenLocations > MAX_DISTANCE_UNTIL_NEW_LOCATION) {
+ // Create new location entry
+ Timber.d("Add new Location to the database!")
+ location = Location(discoveryDate, longitude, latitude, accuracy)
+ locationRepository.insert(location)
+ } else {
+ // If location is within the set limit, just use that location and update lastSeen
+ Timber.d("Location already in the database... Updating the last seen date!")
+ location.lastSeen = discoveryDate
+ locationRepository.update(location)
+
+ }
+
+ Timber.d("Location: $location")
+ }
+ return location
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt
index c656ae8e..69467ef6 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt
@@ -29,6 +29,9 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
@Inject
lateinit var sharedPreferences: SharedPreferences
+ @Inject
+ lateinit var backgroundWorkScheduler: BackgroundWorkScheduler
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -95,6 +98,9 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
super.onResume()
Timber.d("MainActivity onResume called")
BLEScanner.startBluetoothScan(this.applicationContext)
+
+ Timber.d("Scheduling an immediate background scan onResume of MainActivity")
+ backgroundWorkScheduler.scheduleImmediateBackgroundScan()
}
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 e55604ab..e83322ad 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
@@ -31,6 +31,7 @@ import org.osmdroid.views.overlay.CopyrightOverlay
import org.osmdroid.views.overlay.Marker
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
import timber.log.Timber
+import java.security.Permission
object Utility {
@@ -65,6 +66,14 @@ object Utility {
}
}
+ fun checkPermission(permission: String): Boolean {
+ val context = ATTrackingDetectionApplication.getCurrentActivity() ?: return false
+ return ContextCompat.checkSelfPermission(
+ context,
+ permission
+ ) == PackageManager.PERMISSION_GRANTED
+ }
+
fun checkBluetoothPermission(): Boolean {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission(
ATTrackingDetectionApplication.getAppContext(),
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
index 6b7ed2f5..37f53172 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
@@ -8,17 +8,16 @@ import android.content.Intent
import android.os.Build
import androidx.lifecycle.LiveData
import androidx.lifecycle.map
-import androidx.work.ExistingPeriodicWorkPolicy
-import androidx.work.ExistingWorkPolicy
-import androidx.work.Operation
-import androidx.work.WorkInfo
-import androidx.work.WorkManager
+import androidx.work.*
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
+import de.seemoo.at_tracking_detection.BuildConfig
+import de.seemoo.at_tracking_detection.util.Utility
import timber.log.Timber
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
import javax.inject.Inject
import javax.inject.Singleton
+import android.Manifest
@Singleton
class BackgroundWorkScheduler @Inject constructor(
@@ -27,6 +26,13 @@ class BackgroundWorkScheduler @Inject constructor(
) {
fun launch() {
+ // We now scan with Alarms instead of periodic workers, since they have proven to be unreliable
+ scheduleScanWithAlarm()
+ // We cancel all periodic scans
+ workManager.cancelUniqueWork(WorkerConstants.PERIODIC_SCAN_WORKER)
+ }
+
+ fun legacyLaunch() {
Timber.d("Work scheduler started!")
workManager.enqueueUniquePeriodicWork(
WorkerConstants.PERIODIC_SCAN_WORKER,
@@ -38,6 +44,13 @@ class BackgroundWorkScheduler @Inject constructor(
}
}
+ fun scheduleImmediateBackgroundScan() {
+ Timber.d("Scheduling Immediate Background Scan Worker ")
+ workManager.enqueueUniqueWork(WorkerConstants.SCAN_IMMEDIATELY,
+ ExistingWorkPolicy.APPEND_OR_REPLACE,
+ backgroundWorkBuilder.buildImmediateScanWorker())
+ }
+
fun getState(uniqueWorkName: String): LiveData =
workManager.getWorkInfosByTagLiveData(uniqueWorkName).map { it.lastOrNull()?.state }
@@ -108,5 +121,51 @@ class BackgroundWorkScheduler @Inject constructor(
alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
Timber.d("Scheduled an alarm to reschedule the scan at $alarmDate")
}
+
+ fun scheduleScanWithAlarm() {
+ //Run in 15 min
+ val timeInMillisUntilNotification: Long = if (BuildConfig.DEBUG) {
+ 15 * 60 * 1000
+ } else {
+ 15 * 60 * 1000
+ }
+
+ val alarmDate = LocalDateTime.now().plus(timeInMillisUntilNotification, ChronoUnit.MILLIS)
+ val alarmTime = System.currentTimeMillis() + timeInMillisUntilNotification
+
+ val intent = Intent(ATTrackingDetectionApplication.getAppContext(), ScheduleWorkersReceiver::class.java)
+ intent.action = "AlarmManagerWakeUp_Perform_BackgroundScan"
+
+ val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
+ PendingIntent.getBroadcast(ATTrackingDetectionApplication.getAppContext(), -103,intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
+ }else {
+ PendingIntent.getBroadcast(ATTrackingDetectionApplication.getAppContext(), -103, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ }
+
+ val alarmManager = ATTrackingDetectionApplication.getAppContext().getSystemService(
+ Context.ALARM_SERVICE) as AlarmManager
+
+ // We use exact Alarms since we want regular background scans to happen.
+
+ if (Utility.checkPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)) {
+ try {
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ Timber.d("Scheduled an setExactAndAllowWhileIdle alarm to start a scan at $alarmDate")
+ }catch (exception: SecurityException) {
+ try {
+ Timber.w("Failed scheduling setExactAndAllowWhileIdle Alarm scan")
+ // Alarm could not be scheduled because user disallowed battery exception
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ Timber.d("Scheduled an setExact alarm to start a scan at $alarmDate")
+ }catch (exception: SecurityException) {
+ Timber.w("Failed scheduling setExact Alarm scan")
+ }
+ }
+ }else {
+ // Alarm could not be scheduled because user disallowed battery exception
+ alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ Timber.d("Scheduled an set alarm to start a scan at $alarmDate")
+ }
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_device.xml b/app/src/main/res/layout/item_device.xml
index d11c65e2..5ff72b0b 100644
--- a/app/src/main/res/layout/item_device.xml
+++ b/app/src/main/res/layout/item_device.xml
@@ -148,9 +148,8 @@
android:layout_height="wrap_content"
android:autoSizeTextType="uniform"
android:gravity="center"
- android:maxLines="1"
- android:text="@string/beacons"
- android:textSize="10sp"
+ android:maxLines="2"
+ android:text="@string/times_seen"
app:layout_constraintLeft_toLeftOf="@id/device_guideline_middle"
app:layout_constraintRight_toRightOf="@id/device_guideline_right"
app:layout_constraintTop_toTopOf="parent"
@@ -165,7 +164,6 @@
android:maxLines="1"
android:text="@{deviceBeaconCount}"
android:textAppearance="?attr/textAppearanceTitleLarge"
- android:textSize="25sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="@id/device_guideline_middle"
app:layout_constraintRight_toRightOf="@id/device_guideline_right"
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 64c22e6c..2649bfe6 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -349,5 +349,6 @@
Sichere Tracker
Legende
Standort an dem potenzieller Tracker gefunden wurde
+ Anzahl gesehen
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 337454a1..1499da98 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -32,6 +32,7 @@
Expert Mode
Discovered:
Beacons
+ 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.
@@ -287,7 +288,7 @@
Battery Level:
Full
Medium
- Low
+ Low
Very Low
Unknown
From a1570e2106f1f4e96285715bb2b448c8cca19861 Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Sat, 23 Mar 2024 12:20:23 +0100
Subject: [PATCH 110/154] Fixing merge issues with git Testing Background scans
with Alarm
---
.../ScanBluetoothWorkerTest.kt | 37 +-
.../ATTrackingDetectionApplication.kt | 4 +
.../detection/BackgroundBluetoothScanner.kt | 7 +-
.../detection/LocationProvider.kt | 18 +-
.../detection/ScanBluetoothWorker.kt | 336 +-----------------
.../hilt/DatabaseModule.kt | 12 +-
.../at_tracking_detection/ui/MainActivity.kt | 1 +
.../ui/debug/DebugViewModel.kt | 5 +
.../ui/scan/ScanViewModel.kt | 14 +-
.../at_tracking_detection/util/SharedPrefs.kt | 16 +
.../util/ble/BLEScanCallback.kt | 9 +-
.../worker/BackgroundWorkBuilder.kt | 5 +
.../worker/BackgroundWorkScheduler.kt | 4 +
.../worker/ScheduleWorkersReceiver.kt | 68 +++-
.../worker/WorkerConstants.kt | 1 +
app/src/main/res/layout/fragment_debug.xml | 14 +
16 files changed, 163 insertions(+), 388 deletions(-)
diff --git a/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanBluetoothWorkerTest.kt b/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanBluetoothWorkerTest.kt
index fddaf3f9..c739b204 100644
--- a/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanBluetoothWorkerTest.kt
+++ b/app/src/androidTest/java/de/seemoo/at_tracking_detection/ScanBluetoothWorkerTest.kt
@@ -16,6 +16,7 @@ import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
import androidx.work.testing.TestForegroundUpdater
import androidx.work.testing.TestProgressUpdater
import de.seemoo.at_tracking_detection.database.AppDatabase
+import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner
import de.seemoo.at_tracking_detection.detection.LocationProvider
import de.seemoo.at_tracking_detection.detection.LocationRequester
import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker
@@ -60,8 +61,8 @@ class ScanBluetoothWorkerTest {
).addMigrations(DatabaseModule.MIGRATION_5_7, DatabaseModule.MIGRATION_6_7)
.allowMainThreadQueries()
.build().apply {
- openHelper.writableDatabase.close()
- }
+ openHelper.writableDatabase.close()
+ }
this.db = roomDB
executor = Executors.newSingleThreadExecutor()
@@ -76,9 +77,9 @@ class ScanBluetoothWorkerTest {
val scanRepository = DatabaseModule.provideScanRepository(DatabaseModule.provideScanDao(db))
val locationRepository = DatabaseModule.provideLocationRepository(DatabaseModule.provideLocationDao(db))
val locationProvider = LocationProvider(context.getSystemService()!!)
-
val notificationService = ATTrackingDetectionApplication.getCurrentApp()!!.notificationService
val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()!!.backgroundWorkScheduler
+// val backgroundBluetoothScanner = BackgroundBluetoothScanner(backgroundWorkScheduler, notificationService, locationProvider, scanRepository)
val params = WorkerParameters(
UUID.randomUUID(),
@@ -98,7 +99,7 @@ class ScanBluetoothWorkerTest {
val worker = ScanBluetoothWorker(
context,
params,
- scanRepository, locationProvider, notificationService, backgroundWorkScheduler)
+ backgroundWorkScheduler)
runBlocking {
val result = worker.doWork()
@@ -117,6 +118,7 @@ class ScanBluetoothWorkerTest {
val notificationService = ATTrackingDetectionApplication.getCurrentApp()!!.notificationService
val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()!!.backgroundWorkScheduler
+// val backgroundScanner = BackgroundBluetoothScanner(backgroundWorkScheduler, notificationService, locationProvider, scanRepository)
val params = WorkerParameters(
UUID.randomUUID(),
@@ -133,15 +135,11 @@ class ScanBluetoothWorkerTest {
TestForegroundUpdater()
)
- val worker = ScanBluetoothWorker(
- context,
- params,
- scanRepository, locationProvider, notificationService, backgroundWorkScheduler)
runBlocking {
- val result = worker.doWork()
+ val result = BackgroundBluetoothScanner.scanInBackground(startedFrom = "UnitTest")
assertThat(result, instanceOf(ListenableWorker.Result.Success::class.java))
- Assert.assertNotNull(worker.location)
+ Assert.assertNotNull(BackgroundBluetoothScanner.location)
}
}
@@ -157,6 +155,7 @@ class ScanBluetoothWorkerTest {
val notificationService = ATTrackingDetectionApplication.getCurrentApp()!!.notificationService
val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()!!.backgroundWorkScheduler
+// val backgroundScanner = BackgroundBluetoothScanner(backgroundWorkScheduler, notificationService, locationProvider, scanRepository)
val params = WorkerParameters(
UUID.randomUUID(),
@@ -173,15 +172,10 @@ class ScanBluetoothWorkerTest {
TestForegroundUpdater()
)
- val worker = ScanBluetoothWorker(
- context,
- params,
- scanRepository, locationProvider, notificationService, backgroundWorkScheduler)
-
runBlocking {
- val result = worker.doWork()
+ val result = BackgroundBluetoothScanner.scanInBackground(startedFrom = "UnitTest")
assertThat(result, instanceOf(ListenableWorker.Result.Success::class.java))
- Assert.assertNull(worker.location)
+ Assert.assertNull(BackgroundBluetoothScanner.location)
}
}
@@ -198,6 +192,7 @@ class ScanBluetoothWorkerTest {
val notificationService = ATTrackingDetectionApplication.getCurrentApp()!!.notificationService
val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()!!.backgroundWorkScheduler
+
val params = WorkerParameters(
UUID.randomUUID(),
Data.EMPTY,
@@ -213,15 +208,11 @@ class ScanBluetoothWorkerTest {
TestForegroundUpdater()
)
- val worker = ScanBluetoothWorker(
- context,
- params,
- scanRepository, locationProvider, notificationService, backgroundWorkScheduler)
runBlocking {
- val result = worker.doWork()
+ val result = BackgroundBluetoothScanner.scanInBackground(startedFrom = "UnitTest")
assertThat(result, instanceOf(ListenableWorker.Result.Success::class.java))
- Assert.assertNull(worker.location)
+ Assert.assertNull(BackgroundBluetoothScanner.location)
}
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt
index 4c464946..e31d702c 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt
@@ -18,6 +18,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.database.repository.LocationRepository
import de.seemoo.at_tracking_detection.database.repository.NotificationRepository
+import de.seemoo.at_tracking_detection.database.repository.ScanRepository
import de.seemoo.at_tracking_detection.detection.LocationProvider
import de.seemoo.at_tracking_detection.notifications.NotificationService
import de.seemoo.at_tracking_detection.ui.OnboardingActivity
@@ -62,6 +63,9 @@ class ATTrackingDetectionApplication : Application(), Configuration.Provider {
@Inject
lateinit var locationProvider: LocationProvider
+ @Inject
+ lateinit var scanRepository: ScanRepository
+
private val activityLifecycleCallbacks = ATTDLifecycleCallbacks()
override fun getWorkManagerConfiguration() =
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 f17a0e89..4500b14b 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
@@ -74,11 +74,11 @@ object BackgroundBluetoothScanner {
return ATTrackingDetectionApplication.getCurrentApp()?.scanRepository
}
- var isScanning = false
+ public var isScanning = false
class BackgroundScanResults(var duration: Long, var scanMode: Int, var numberDevicesFound: Int, var failed: Boolean)
suspend fun scanInBackground(startedFrom: String): BackgroundScanResults {
if (isScanning) {
- Timber.w("BLE background scan already running")
+ Timber.w("BackgroundBluetoothScanner scan already running")
return BackgroundScanResults(0, 0, 0, true)
}
@@ -130,6 +130,8 @@ object BackgroundBluetoothScanner {
val scanDuration: Long = getScanDuration()
delay(scanDuration)
BLEScanCallback.stopScanning(bluetoothAdapter.bluetoothLeScanner)
+ isScanning = false
+
Timber.d("Scanning for bluetooth le devices stopped!. Discovered ${scanResultDictionary.size} devices")
//Waiting for updated location to come in
@@ -174,7 +176,6 @@ object BackgroundBluetoothScanner {
backgroundWorkScheduler?.scheduleTrackingDetector()
BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
- isScanning = true
// Release the wake lock when we are done
wakeLock.release()
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 fe5eb3a9..d8087f10 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
@@ -9,6 +9,7 @@ import android.os.*
import androidx.core.content.ContextCompat
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import timber.log.Timber
+import java.time.LocalDateTime
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@@ -126,7 +127,16 @@ open class LocationProvider @Inject constructor(
}
private fun locationMatchesMinimumRequirements(location: Location): Boolean {
- return location.accuracy <= MIN_ACCURACY_METER && getSecondsSinceLocation(location) <= MAX_AGE_SECONDS
+ if (location.accuracy <= MIN_ACCURACY_METER) {
+ if (getSecondsSinceLocation(location) <= MAX_AGE_SECONDS) {
+ return true
+ }else {
+ Timber.d("Location too old")
+ }
+ }else {
+ Timber.d("Location accuracy is not good enough")
+ }
+ return false
}
@@ -221,6 +231,7 @@ open class LocationProvider @Inject constructor(
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
+ Timber.w("Not requesting location, permission not granted")
return
}
@@ -239,6 +250,8 @@ open class LocationProvider @Inject constructor(
)
}
+ Timber.i("Requesting location updates from $enabledProviders")
+
// If no location providers are enabled, log an error and stop location updates
if (enabledProviders.isEmpty()) {
Timber.e("ERROR: No location provider available")
@@ -248,10 +261,11 @@ open class LocationProvider @Inject constructor(
private fun stopLocationUpdates() {
locationManager.removeUpdates(this)
+ Timber.i("Stopping location updates")
}
override fun onLocationChanged(location: Location) {
- Timber.d("Location updated: ${location.latitude} ${location.longitude}")
+ Timber.d("Location updated: ${location.latitude} ${location.longitude}, accuracy: ${location.accuracy}, date: ${Date(location.time)}")
val bestLastLocation = this.bestLastLocation
if (bestLastLocation == null) {
this.bestLastLocation = location
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 07b43684..420060db 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
@@ -40,347 +40,25 @@ import kotlin.coroutines.suspendCoroutine
class ScanBluetoothWorker @AssistedInject constructor(
@Assisted appContext: Context,
@Assisted workerParams: WorkerParameters,
- private val scanRepository: ScanRepository,
- private val locationProvider: LocationProvider,
- private val notificationService: NotificationService,
- var backgroundWorkScheduler: BackgroundWorkScheduler
+ var backgroundWorkScheduler: BackgroundWorkScheduler,
) :
CoroutineWorker(appContext, workerParams) {
- private lateinit var bluetoothAdapter: BluetoothAdapter
-
- private var scanResultDictionary: ConcurrentHashMap = ConcurrentHashMap()
-
- var location: Location? = null
- set(value) {
- field = value
- if (value != null) {
- locationRetrievedCallback?.let { it() }
- }
- }
-
- private var locationRetrievedCallback: (() -> Unit)? = null
-
- private var locationFetchStarted: Long? = null
override suspend fun doWork(): Result {
- Timber.d("Bluetooth scanning worker started!")
- val scanMode = getScanMode()
- val scanId = scanRepository.insert(Scan(startDate = LocalDateTime.now(), isManual = false, scanMode = scanMode))
-
- if (!Utility.checkBluetoothPermission()) {
- Timber.d("Permission to perform bluetooth scan missing")
- return Result.retry()
- }
- try {
- val bluetoothManager =
- applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
- bluetoothAdapter = bluetoothManager.adapter
- } catch (e: Throwable) {
- Timber.e("BluetoothAdapter not found!")
- return Result.retry()
- }
- scanResultDictionary = ConcurrentHashMap()
+ val results = BackgroundBluetoothScanner.scanInBackground(startedFrom = "ScanBluetoothWorker")
- val useLocation = SharedPrefs.useLocationInTrackingDetection
- if (useLocation) {
- // Returns the last known location if this matches our requirements or starts new location updates
- locationFetchStarted = System.currentTimeMillis()
- location = locationProvider.lastKnownOrRequestLocationUpdates(locationRequester = locationRequester, timeoutMillis = LOCATION_UPDATE_MAX_TIME_MS - 2000L)
- }
-
- //Starting BLE Scan
- Timber.d("Start Scanning for bluetooth le devices...")
- val scanSettings =
- ScanSettings.Builder().setScanMode(scanMode).build()
-
- SharedPrefs.isScanningInBackground = true
- BLEScanCallback.startScanning(bluetoothAdapter.bluetoothLeScanner, DeviceManager.scanFilter, scanSettings, leScanCallback)
-
- val scanDuration: Long = getScanDuration()
- delay(scanDuration)
- BLEScanCallback.stopScanning(bluetoothAdapter.bluetoothLeScanner)
- Timber.d("Scanning for bluetooth le devices stopped!. Discovered ${scanResultDictionary.size} devices")
-
- //Waiting for updated location to come in
- val fetchedLocation = waitForRequestedLocation()
- Timber.d("Fetched location? $fetchedLocation")
- if (location == null) {
- // Get the last location no matter if the requirements match or not
- location = locationProvider.getLastLocation(checkRequirements = false)
- }
-
- val validDeviceTypes = DeviceType.getAllowedDeviceTypesFromSettings()
-
- //Adding all scan results to the database after the scan has finished
- scanResultDictionary.forEach { (_, discoveredDevice) ->
- val deviceType = DeviceManager.getDeviceType(discoveredDevice.scanResult)
-
- if (deviceType in validDeviceTypes) {
- insertScanResult(
- discoveredDevice.scanResult,
- location?.latitude,
- location?.longitude,
- location?.accuracy,
- discoveredDevice.discoveryDate,
- )
- }
- }
-
- SharedPrefs.lastScanDate = LocalDateTime.now()
- SharedPrefs.isScanningInBackground = false
- val scan = scanRepository.scanWithId(scanId.toInt())
- if (scan != null) {
- scan.endDate = LocalDateTime.now()
- scan.duration = scanDuration.toInt() / 1000
- scan.noDevicesFound = scanResultDictionary.size
- scanRepository.update(scan)
+ if (results.failed) {
+ return Result.retry()
}
- Timber.d("Scheduling tracking detector worker")
- backgroundWorkScheduler.scheduleTrackingDetector()
- BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
-
return Result.success(
Data.Builder()
- .putLong("duration", scanDuration)
- .putInt("mode", scanMode)
- .putInt("devicesFound", scanResultDictionary.size)
+ .putLong("duration", results.duration)
+ .putInt("mode", results.scanMode)
+ .putInt("devicesFound", results.numberDevicesFound)
.build()
)
}
-
- private val leScanCallback: ScanCallback = object : ScanCallback() {
- override fun onScanResult(callbackType: Int, scanResult: ScanResult) {
- super.onScanResult(callbackType, scanResult)
- //Checks if the device has been found already
- if (!scanResultDictionary.containsKey(getPublicKey(scanResult))) {
- Timber.d("Found ${scanResult.device.address} at ${LocalDateTime.now()}")
- scanResultDictionary[getPublicKey(scanResult)] =
- DiscoveredDevice(scanResult, LocalDateTime.now())
- }
- }
-
- override fun onScanFailed(errorCode: Int) {
- super.onScanFailed(errorCode)
- Timber.e("Bluetooth scan failed $errorCode")
- if (BuildConfig.DEBUG) {
- notificationService.sendBLEErrorNotification()
- }
- }
- }
-
- private val locationRequester: LocationRequester = object : LocationRequester() {
- override fun receivedAccurateLocationUpdate(location: Location) {
- val started = locationFetchStarted ?: System.currentTimeMillis()
- Timber.d("Got location in ${(System.currentTimeMillis()-started)/1000}s")
- this@ScanBluetoothWorker.location = location
- this@ScanBluetoothWorker.locationRetrievedCallback?.let { it() }
- }
- }
-
- private fun getScanMode(): Int {
- val useLowPower = SharedPrefs.useLowPowerBLEScan
- return if (useLowPower) {
- ScanSettings.SCAN_MODE_LOW_POWER
- } else {
- ScanSettings.SCAN_MODE_LOW_LATENCY
- }
- }
-
- private fun getScanDuration(): Long {
- val useLowPower = SharedPrefs.useLowPowerBLEScan
- return if (useLowPower) {
- 30_000L
- } else {
- 20_000L
- }
- }
-
- private suspend fun waitForRequestedLocation(): Boolean {
- if (location != null || !SharedPrefs.useLocationInTrackingDetection) {
- //Location already there. Just return
- return true
- }
-
- return suspendCoroutine { cont ->
- var coroutineFinished = false
-
- val handler = Handler(Looper.getMainLooper())
- val runnable = Runnable {
- if (!coroutineFinished) {
- coroutineFinished = true
- locationRetrievedCallback = null
- Timber.d("Could not get location update in time.")
- cont.resume(false)
- }
- }
-
- locationRetrievedCallback = {
- if (!coroutineFinished) {
- handler.removeCallbacks(runnable)
- coroutineFinished = true
- cont.resume(true)
- }
- }
-
- // Fallback if no location is fetched in time
- val maximumLocationDurationMillis = LOCATION_UPDATE_MAX_TIME_MS
- handler.postDelayed(runnable, maximumLocationDurationMillis)
- }
- }
-
- class DiscoveredDevice(var scanResult: ScanResult, var discoveryDate: LocalDateTime)
-
- companion object {
- const val MAX_DISTANCE_UNTIL_NEW_LOCATION: Float = 150f // in meters
- const val TIME_BETWEEN_BEACONS: Long = 15 // 15 minutes until the same beacon gets saved again in the db
- const val LOCATION_UPDATE_MAX_TIME_MS: Long = 122_000L // Wait maximum 122s to get a location update
-
- suspend fun insertScanResult(
- scanResult: ScanResult,
- latitude: Double?,
- longitude: Double?,
- accuracy: Float?,
- discoveryDate: LocalDateTime,
- ): Pair {
- val deviceSaved = saveDevice(scanResult, discoveryDate) ?: return Pair(null, null) // return when device does not qualify to be saved
-
- // set locationId to null if gps location could not be retrieved
- val locId: Int? = saveLocation(latitude, longitude, discoveryDate, accuracy)?.locationId
-
- val beaconSaved = saveBeacon(scanResult, discoveryDate, locId) ?: return Pair(null, null)
-
- return Pair(deviceSaved, beaconSaved)
- }
-
- private suspend fun saveBeacon(
- scanResult: ScanResult,
- discoveryDate: LocalDateTime,
- locId: Int?
- ): Beacon? {
- val beaconRepository = ATTrackingDetectionApplication.getCurrentApp()?.beaconRepository ?: return null
- val uuids = scanResult.scanRecord?.serviceUuids?.map { it.toString() }?.toList()
- val uniqueIdentifier = getPublicKey(scanResult)
-
- val connectionState: ConnectionState = BaseDevice.getConnectionState(scanResult)
- val connectionStateString = Utility.connectionStateToString(connectionState)
-
- var beacon: Beacon? = null
- val beacons = beaconRepository.getDeviceBeaconsSince(
- deviceAddress = uniqueIdentifier,
- since = discoveryDate.minusMinutes(TIME_BETWEEN_BEACONS)
- ) // sorted by newest first
-
- if (beacons.isEmpty()) {
- Timber.d("Add new Beacon to the database!")
- beacon = if (BuildConfig.DEBUG) {
- // Save the manufacturer data to the beacon
- Beacon(
- discoveryDate, scanResult.rssi, getPublicKey(scanResult), locId,
- scanResult.scanRecord?.bytes, uuids, connectionStateString
- )
- } else {
- Beacon(
- discoveryDate, scanResult.rssi, getPublicKey(scanResult), locId,
- null, uuids, connectionStateString
- )
- }
- beaconRepository.insert(beacon)
- } else if (beacons[0].locationId == null && locId != null && locId != 0){
- // Update beacon within the last TIME_BETWEEN_BEACONS minutes with location
- Timber.d("Beacon already in the database... Adding Location")
- beacon = beacons[0]
- beacon.locationId = locId
- if (beacon.connectionState == "UNKNOWN" && connectionState != ConnectionState.UNKNOWN) {
- beacon.connectionState = connectionStateString
- }
- beaconRepository.update(beacon)
- }
-
- Timber.d("Beacon: $beacon")
-
- return beacon
- }
-
- private suspend fun saveDevice(
- scanResult: ScanResult,
- discoveryDate: LocalDateTime
- ): BaseDevice? {
- val deviceRepository = ATTrackingDetectionApplication.getCurrentApp()?.deviceRepository ?: return null
-
- val deviceAddress = getPublicKey(scanResult)
-
- // Checks if Device already exists in device database
- var device = deviceRepository.getDevice(deviceAddress)
- if (device == null) {
- // Do not Save Samsung Devices
- device = BaseDevice(scanResult)
-
- // Check if ConnectionState qualifies Device to be saved
- // Only Save when Device is offline long enough
- if (BaseDevice.getConnectionState(scanResult) !in DeviceManager.savedConnectionStates) {
- Timber.d("Device not in a saved connection state... Skipping!")
- return null
- }
-
- if (BaseDevice.getConnectionState(scanResult) !in DeviceManager.unsafeConnectionState) {
- Timber.d("Device is safe and will be hidden to the user!")
- device.safeTracker = true
- }
-
- Timber.d("Add new Device to the database!")
- deviceRepository.insert(device)
- } else {
- Timber.d("Device already in the database... Updating the last seen date!")
- device.lastSeen = discoveryDate
- deviceRepository.update(device)
- }
-
- Timber.d("Device: $device")
- return device
- }
-
- private suspend fun saveLocation(
- latitude: Double?,
- longitude: Double?,
- discoveryDate: LocalDateTime,
- accuracy: Float?
- ): LocationModel? {
- val locationRepository = ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return null
-
- // set location to null if gps location could not be retrieved
- var location: LocationModel? = null
-
- if (latitude != null && longitude != null) {
- // Get closest location from database
- location = locationRepository.closestLocation(latitude, longitude)
-
- var distanceBetweenLocations: Float = Float.MAX_VALUE
-
- if (location != null) {
- val locationA = getLocation(latitude, longitude)
- val locationB = getLocation(location.latitude, location.longitude)
- distanceBetweenLocations = locationA.distanceTo(locationB)
- }
-
- if (location == null || distanceBetweenLocations > MAX_DISTANCE_UNTIL_NEW_LOCATION) {
- // Create new location entry
- Timber.d("Add new Location to the database!")
- location = LocationModel(discoveryDate, longitude, latitude, accuracy)
- locationRepository.insert(location)
- } else {
- // If location is within the set limit, just use that location and update lastSeen
- Timber.d("Location already in the database... Updating the last seen date!")
- location.lastSeen = discoveryDate
- locationRepository.update(location)
-
- }
-
- Timber.d("Location: $location")
- }
- return location
- }
- }
}
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 e3ad2a3d..cd270e84 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
@@ -13,7 +13,7 @@ import dagger.hilt.components.SingletonComponent
import de.seemoo.at_tracking_detection.database.AppDatabase
import de.seemoo.at_tracking_detection.database.daos.*
import de.seemoo.at_tracking_detection.database.repository.*
-import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker.Companion.MAX_DISTANCE_UNTIL_NEW_LOCATION
+import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner
import de.seemoo.at_tracking_detection.detection.TrackingDetectorWorker.Companion.getLocation
import timber.log.Timber
import javax.inject.Singleton
@@ -27,7 +27,7 @@ object DatabaseModule {
try {
db.execSQL("ALTER TABLE `beacon` ADD COLUMN `serviceUUIDs` TEXT DEFAULT NULL")
}catch (e: SQLiteException) {
- Timber.e("Could not create new column $e")
+ Timber.e("Could not create new column ${e}")
}
}
@@ -46,7 +46,7 @@ object DatabaseModule {
db.execSQL("CREATE UNIQUE INDEX `index_location_latitude_longitude` ON `location` (`latitude`, `longitude`)")
db.execSQL("ALTER TABLE `beacon` ADD COLUMN `locationId` INTEGER")
}catch (e: SQLiteException) {
- Timber.e("Could not create location $e")
+ Timber.e("Could not create location ${e}")
}
var sql: String
@@ -80,7 +80,7 @@ object DatabaseModule {
val locationA = getLocation(latitude, longitude)
val locationB = getLocation(closestLatitude, closestLongitude)
val distanceBetweenLocations = locationA.distanceTo(locationB)
- if (distanceBetweenLocations > MAX_DISTANCE_UNTIL_NEW_LOCATION){
+ if (distanceBetweenLocations > BackgroundBluetoothScanner.MAX_DISTANCE_UNTIL_NEW_LOCATION){
// println("Insert New, because far enough away")
insertNewLocation = true
} else {
@@ -138,7 +138,7 @@ object DatabaseModule {
db.execSQL("INSERT INTO `beacon_backup` SELECT `beaconId`, `receivedAt`, `rssi`, `deviceAddress`, `locationId`, `mfg`, `serviceUUIDs` FROM `beacon`")
db.execSQL("DROP TABLE `beacon`")
} catch (e: SQLiteException) {
- Timber.e("Could not create beacon_backup $e")
+ Timber.e("Could not create beacon_backup ${e}")
}
try {
@@ -146,7 +146,7 @@ object DatabaseModule {
db.execSQL("INSERT INTO `beacon` SELECT `beaconId`, `receivedAt`, `rssi`, `deviceAddress`, `locationId`, `mfg`, `serviceUUIDs` FROM `beacon_backup`")
db.execSQL("DROP TABLE `beacon_backup`")
} catch (e: SQLiteException) {
- Timber.e("Could not create beacon $e")
+ Timber.e("Could not create beacon ${e}")
}
}
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt
index 69467ef6..8a54b931 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/MainActivity.kt
@@ -17,6 +17,7 @@ import de.seemoo.at_tracking_detection.BuildConfig
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.util.SharedPrefs
import de.seemoo.at_tracking_detection.util.ble.BLEScanner
+import de.seemoo.at_tracking_detection.worker.BackgroundWorkScheduler
import org.osmdroid.config.Configuration
import timber.log.Timber
import java.io.File
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugViewModel.kt
index fbe6bc46..4f7b1635 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/debug/DebugViewModel.kt
@@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import de.seemoo.at_tracking_detection.util.SharedPrefs
+import java.time.LocalDateTime
import javax.inject.Inject
@HiltViewModel
@@ -24,6 +25,8 @@ class DebugViewModel @Inject constructor(
var scanText = MutableLiveData("Not scanning")
+ var nextScanDate = MutableLiveData(SharedPrefs.nextScanDate.toString())
+ var lastScanDate = MutableLiveData(SharedPrefs.lastScanDate.toString())
init {
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
@@ -36,5 +39,7 @@ class DebugViewModel @Inject constructor(
}else {
scanText.postValue("Not scanning")
}
+ nextScanDate.postValue(SharedPrefs.nextScanDate.toString())
+ lastScanDate.postValue(SharedPrefs.lastScanDate.toString())
}
}
\ No newline at end of file
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 fde8d0ea..7c87e137 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
@@ -17,9 +17,9 @@ import de.seemoo.at_tracking_detection.database.models.device.DeviceManager.getD
import de.seemoo.at_tracking_detection.database.models.device.DeviceType.Companion.getAllowedDeviceTypesFromSettings
import de.seemoo.at_tracking_detection.database.repository.BeaconRepository
import de.seemoo.at_tracking_detection.database.repository.ScanRepository
+import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner
+import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner.TIME_BETWEEN_BEACONS
import de.seemoo.at_tracking_detection.detection.LocationProvider
-import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker
-import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker.Companion.TIME_BETWEEN_BEACONS
//import de.seemoo.at_tracking_detection.util.Utility
import de.seemoo.at_tracking_detection.util.ble.BLEScanner
import kotlinx.coroutines.MainScope
@@ -33,7 +33,7 @@ class ScanViewModel @Inject constructor(
private val scanRepository: ScanRepository,
private val beaconRepository: BeaconRepository,
private val locationProvider: LocationProvider,
- ) : ViewModel() {
+) : ViewModel() {
val bluetoothDeviceListHighRisk = MutableLiveData>() // TODO: Problem needs to be immutable so that DiffUtil works
val bluetoothDeviceListLowRisk = MutableLiveData>()
@@ -71,15 +71,15 @@ class ScanViewModel @Inject constructor(
val currentDate = LocalDateTime.now()
val uniqueIdentifier = getPublicKey(scanResult) // either public key or MAC-Address
if (beaconRepository.getNumberOfBeaconsAddress(
- deviceAddress = uniqueIdentifier,
- since = currentDate.minusMinutes(TIME_BETWEEN_BEACONS)
- ) == 0) {
+ deviceAddress = uniqueIdentifier,
+ since = currentDate.minusMinutes(TIME_BETWEEN_BEACONS)
+ ) == 0) {
// There was no beacon with the address saved in the last IME_BETWEEN_BEACONS minutes
val location = locationProvider.getLastLocation() // if not working: checkRequirements = false
Timber.d("Got location $location in ScanViewModel")
MainScope().async {
- ScanBluetoothWorker.insertScanResult(
+ BackgroundBluetoothScanner.insertScanResult(
scanResult = scanResult,
latitude = location?.latitude,
longitude = location?.longitude,
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 d4a286e4..864484e7 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
@@ -44,6 +44,22 @@ object SharedPrefs {
sharedPreferences.edit().putString("last_scan", value.toString()).apply()
}
+ var nextScanDate: LocalDateTime?
+ get() {
+ val dateString = sharedPreferences.getString("next_scan", null)
+ if (dateString != null) {
+ return try {
+ LocalDateTime.parse(dateString)
+ }catch(e: DateTimeParseException) {
+ null
+ }
+ }
+ return null
+ }
+ set(value) {
+ sharedPreferences.edit().putString("next_scan", value.toString()).apply()
+ }
+
var shareData: Boolean
get() {
return sharedPreferences.getBoolean("share_data", false)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/BLEScanCallback.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/BLEScanCallback.kt
index d91d4cbb..cbe17aaa 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/BLEScanCallback.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/ble/BLEScanCallback.kt
@@ -39,8 +39,13 @@ object BLEScanCallback {
override fun onScanFailed(errorCode: Int) {
super.onScanFailed(errorCode)
- Timber.e("BLE Scan failed. $errorCode")
- scanCallback?.get()?.onScanFailed(errorCode)
+ if (errorCode != 1 ) {
+ Timber.e("BLE Scan failed. $errorCode")
+ scanCallback?.get()?.onScanFailed(errorCode)
+ }else {
+ // errorCode = 1 means that scan is already running
+ Timber.w("BLE Scan already running.")
+ }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkBuilder.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkBuilder.kt
index 7a6fa0dc..df037d0d 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkBuilder.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkBuilder.kt
@@ -20,6 +20,11 @@ class BackgroundWorkBuilder @Inject constructor() {
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkerConstants.KIND_DELAY, TimeUnit.SECONDS)
.build()
+ fun buildImmediateScanWorker(): OneTimeWorkRequest =
+ OneTimeWorkRequestBuilder().addTag(WorkerConstants.SCAN_IMMEDIATELY)
+ .setBackoffCriteria(BackoffPolicy.LINEAR, WorkerConstants.KIND_DELAY, TimeUnit.MINUTES)
+ .build()
+
fun buildSendStatisticsWorker(): PeriodicWorkRequest =
PeriodicWorkRequestBuilder(
WorkerConstants.MIN_HOURS_TO_NEXT_SEND_STATISTICS, TimeUnit.HOURS
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
index 37f53172..21e1db10 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
@@ -18,6 +18,7 @@ import java.time.temporal.ChronoUnit
import javax.inject.Inject
import javax.inject.Singleton
import android.Manifest
+import de.seemoo.at_tracking_detection.util.SharedPrefs
@Singleton
class BackgroundWorkScheduler @Inject constructor(
@@ -51,6 +52,7 @@ class BackgroundWorkScheduler @Inject constructor(
backgroundWorkBuilder.buildImmediateScanWorker())
}
+
fun getState(uniqueWorkName: String): LiveData =
workManager.getWorkInfosByTagLiveData(uniqueWorkName).map { it.lastOrNull()?.state }
@@ -166,6 +168,8 @@ class BackgroundWorkScheduler @Inject constructor(
alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
Timber.d("Scheduled an set alarm to start a scan at $alarmDate")
}
+
+ SharedPrefs.nextScanDate = alarmDate
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/ScheduleWorkersReceiver.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/ScheduleWorkersReceiver.kt
index 2a2db16f..c30b5a4f 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/ScheduleWorkersReceiver.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/ScheduleWorkersReceiver.kt
@@ -7,24 +7,50 @@ import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
+import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner
import de.seemoo.at_tracking_detection.detection.ScanBluetoothWorker
import de.seemoo.at_tracking_detection.util.SharedPrefs
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
-class ScheduleWorkersReceiver : BroadcastReceiver() {
+class ScheduleWorkersReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Timber.d("Broadcast received ${intent?.action}")
- val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()?.backgroundWorkScheduler
- //Enqueue the scan task
- backgroundWorkScheduler?.launch()
- if (SharedPrefs.shareData) {
- backgroundWorkScheduler?.scheduleShareData()
- }
- BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
- Timber.d("Scheduled background work")
- ATTrackingDetectionApplication.getCurrentApp()?.notificationService?.scheduleSurveyNotification(false)
+ if (intent?.action == "AlarmManagerWakeUp_Schedule_BackgroundScan") {
+ // The app has been launched because no scan was performed since two hours
+ val backgroundWorkScheduler = ATTrackingDetectionApplication.getCurrentApp()?.backgroundWorkScheduler
+ //Schedule the periodic scan worker which runs every 15min
+ backgroundWorkScheduler?.launch()
+ if (SharedPrefs.shareData) {
+ backgroundWorkScheduler?.scheduleShareData()
+ }
+ BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
+ }else {
+ // action = AlarmManagerWakeUp_Perform_BackgroundScan
+ // The app has been launched to perform another scan
+ BackgroundWorkScheduler.scheduleScanWithAlarm()
+ val app = ATTrackingDetectionApplication?.getCurrentApp()
+ if (app != null) {
+
+ @OptIn(DelicateCoroutinesApi::class)
+ GlobalScope.launch {
+ Timber.d("Running scan launched from Alert")
+ BackgroundBluetoothScanner.scanInBackground(startedFrom = "ScheduleWorkersReceiver")
+ }
+
+ }else {
+ Timber.d("Could not find required dependencies")
+ }
+ }
}
@@ -51,11 +77,21 @@ class ScheduleWorkersReceiver : BroadcastReceiver() {
WorkManager.getInstance(context).enqueue(workRequestObserveTracker)
}
-// fun cancelWorker(context: Context, deviceAddress: String) {
-// val workManager = WorkManager.getInstance(context)
-// val workRequests = workManager.getWorkInfosByTag(deviceAddress)
-//
-//
-// }
+ }
+}
+
+// From: https://stackoverflow.com/questions/74111692/run-coroutine-functions-on-broadcast-receiver
+fun BroadcastReceiver.goAsync(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> Unit
+) {
+ val pendingResult = goAsync()
+ @OptIn(DelicateCoroutinesApi::class) // Must run globally; there's no teardown callback.
+ GlobalScope.launch(context) {
+ try {
+ block()
+ } finally {
+ pendingResult.finish()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/WorkerConstants.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/WorkerConstants.kt
index 8578c0d5..dc148782 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/WorkerConstants.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/WorkerConstants.kt
@@ -4,6 +4,7 @@ object WorkerConstants {
const val MIN_MINUTES_TO_NEXT_BT_SCAN = 15L
const val MIN_HOURS_TO_NEXT_SEND_STATISTICS = 4L
const val PERIODIC_SCAN_WORKER = "PeriodicScanWorker"
+ const val SCAN_IMMEDIATELY = "PeriodicScanWorker_NOW"
const val PERIODIC_SEND_STATISTICS_WORKER = "PeriodicSendStatisticsWorker"
const val ONETIME_SEND_STATISTICS_WORKER = "OneTimeSendStatisticsWorker"
const val TRACKING_DETECTION_WORKER = "TrackingDetectionWorker"
diff --git a/app/src/main/res/layout/fragment_debug.xml b/app/src/main/res/layout/fragment_debug.xml
index 0978275e..bd5fa295 100644
--- a/app/src/main/res/layout/fragment_debug.xml
+++ b/app/src/main/res/layout/fragment_debug.xml
@@ -37,6 +37,20 @@
android:text="@{vm.scanText}"
/>
+
+
+
+
Date: Sat, 23 Mar 2024 12:37:56 +0100
Subject: [PATCH 111/154] upgrade gradle to 8.3.1, upgrade dependencies
---
app/build.gradle | 8 ++++----
build.gradle | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 5ff2301e..f45fc221 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -160,15 +160,15 @@ dependencies {
// Integration with activities
implementation 'androidx.activity:activity-compose:1.8.2'
// Compose Material Design
- implementation 'androidx.compose.material:material:1.6.3'
+ implementation 'androidx.compose.material:material:1.6.4'
// Animations
- implementation 'androidx.compose.animation:animation:1.6.3'
+ implementation 'androidx.compose.animation:animation:1.6.4'
// Tooling support (Previews, etc.)
- implementation 'androidx.compose.ui:ui-tooling:1.6.3'
+ implementation 'androidx.compose.ui:ui-tooling:1.6.4'
// Integration with ViewModels
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
// UI Tests
- androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.3'
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.4'
// When using a MDC theme
implementation "com.google.android.material:compose-theme-adapter:1.2.1"
diff --git a/build.gradle b/build.gradle
index 6a0e058e..97be67b0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@ buildscript {
}
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.3.0'
+ classpath 'com.android.tools.build:gradle:8.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7"
From ed860fa59d764d8f808c482413f354c7492a0f7e Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 23 Mar 2024 14:31:16 +0100
Subject: [PATCH 112/154] replace some Strings with Plurals
---
.../notifications/NotificationBuilder.kt | 8 +++++---
app/src/main/res/values-de/strings.xml | 5 ++++-
app/src/main/res/values/strings.xml | 7 ++++---
3 files changed, 13 insertions(+), 7 deletions(-)
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 b12a4365..e4b2e0d9 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
@@ -100,9 +100,11 @@ class NotificationBuilder @Inject constructor(
): Notification {
Timber.d("Notification with id $notificationId for device $deviceAddress has been build!")
val bundle: Bundle = packBundle(deviceAddress, notificationId)
- val notifyText = context.getString(
- R.string.notification_text_base,
- RiskLevelEvaluator.getMinutesAtLeastTrackedBeforeAlarm()
+ val minutesAtLeastTracked = RiskLevelEvaluator.getMinutesAtLeastTrackedBeforeAlarm()
+ val notifyText = context.resources.getQuantityString(
+ R.plurals.notification_text_base,
+ minutesAtLeastTracked.toInt(),
+ minutesAtLeastTracked
)
var notification = NotificationCompat.Builder(context, NotificationConstants.CHANNEL_ID)
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 2649bfe6..2e76e945 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -224,7 +224,10 @@
%s folgen dir
Öffnen, um weitere Informationen zu erhalten. Die %s ist Dir für mindestens %d Minuten gefolgt.
Öffnen, um weitere Informationen zu erhalten. Der %s ist Dir für mindestens %d Minuten gefolgt.
- Öffnen, um weitere Informationen zu erhalten. Ein Gerät ist Dir für mindestens %d Minuten gefolgt.
+
+ - Öffnen, um weitere Informationen zu erhalten. Ein Gerät ist Dir für mindestens einer Minute gefolgt.
+ - Öffnen, um weitere Informationen zu erhalten. Ein Gerät ist Dir für mindestens %d Minuten gefolgt.
+
Ein Tracker folgt dir
Ignorierte Geräte
Gefundene AirTags
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1499da98..57a06626 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -132,8 +132,6 @@
Find My Device %s
Your observed tracker
- A tracker you observed has been following you for at least an hour.
- A tracker you observed has been following you for at least %d hours.
- A tracker you observed has been following you for at least one hour.
- A tracker you observed has been following you for at least %d hours.
@@ -141,7 +139,10 @@
A tracker you observed is not following you.
An error occurred while observing this tracker. Please try again.
- Open the notification to get more info. A tracker has followed you for at least %d minutes.
+
+ - Open the notification to get more info. A tracker has followed you for at least one minute.
+ - Open the notification to get more info. A tracker has followed you for at least %d minutes.
+
A tracker has been following you
Open the notification to get more info. The %s has followed you for at least %d minutes.
Open the notification to get more info. The %s have followed you for at least %d minutes.
From 7032a096d6598368929dd68af2f46ac38a17a732 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 23 Mar 2024 14:33:49 +0100
Subject: [PATCH 113/154] fix crash when double tapping scan result
---
.../ui/scan/BluetoothDeviceAdapter.kt | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
index 3d48d00a..e5a71d1b 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
@@ -11,6 +11,7 @@ import com.google.android.material.card.MaterialCardView
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
import de.seemoo.at_tracking_detection.databinding.ItemScanResultBinding
+import timber.log.Timber
class BluetoothDeviceAdapter:
ListAdapter(BluetoothDeviceDiffCallback()) {
@@ -37,10 +38,14 @@ class BluetoothDeviceAdapter:
holder.itemView.findViewById(R.id.scan_result_item_card)
.setOnClickListener {
- val deviceAddress: String = getPublicKey(scanResult)
- val directions = ScanFragmentDirections.actionScanToTrackingFragment(deviceAddress)
- holder.itemView.findNavController()
- .navigate(directions)
+ try {
+ val deviceAddress: String = getPublicKey(scanResult)
+ val directions = ScanFragmentDirections.actionScanToTrackingFragment(deviceAddress)
+ holder.itemView.findNavController()
+ .navigate(directions)
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
}
}
}
From 15a0150345fe1920100345ee1335faa816403aeb Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 23 Mar 2024 14:58:58 +0100
Subject: [PATCH 114/154] add caching to deviceType retrieval and fix bug in
old Beacon deletion
---
.../database/daos/BeaconDao.kt | 2 +-
.../database/models/device/DeviceManager.kt | 17 ++++++++++++++++-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/BeaconDao.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/BeaconDao.kt
index bc6baf96..3e240275 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/BeaconDao.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/daos/BeaconDao.kt
@@ -73,6 +73,6 @@ interface BeaconDao {
@Delete
suspend fun deleteBeacons(beacons: List)
- @Query("SELECT * FROM beacon LEFT JOIN notification ON beacon.deviceAddress = notification.deviceAddress WHERE receivedAt < :deleteEverythingBefore AND notification.deviceAddress IS NULL")
+ @Query("SELECT * FROM beacon LEFT JOIN notification ON beacon.deviceAddress = notification.deviceAddress WHERE receivedAt < :deleteEverythingBefore AND notification.deviceAddress IS NULL AND beacon.deviceAddress IS NOT NULL AND beacon.deviceAddress <> ''")
fun getBeaconsOlderThanWithoutNotifications(deleteEverythingBefore: LocalDateTime): List
}
\ 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 2444a0f0..ccef4ff5 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
@@ -15,8 +15,23 @@ object DeviceManager {
val savedConnectionStates = enumValues().toList()
val unsafeConnectionState = listOf(ConnectionState.OVERMATURE_OFFLINE, ConnectionState.UNKNOWN)
+ private val deviceTypeCache = mutableMapOf()
+
fun getDeviceType(scanResult: ScanResult): DeviceType {
- Timber.d("Checking device type for ${scanResult.device.address}")
+ val deviceAddress = scanResult.device.address
+
+ // Check cache, before calculating again
+ deviceTypeCache[deviceAddress]?.let { cachedDeviceType ->
+ return cachedDeviceType
+ }
+
+ val deviceType = calculateDeviceType(scanResult)
+ deviceTypeCache[deviceAddress] = deviceType
+ return deviceType
+ }
+
+ private fun calculateDeviceType(scanResult: ScanResult): DeviceType {
+ Timber.d("Retrieving device type for ${scanResult.device.address}")
scanResult.scanRecord?.let { scanRecord ->
scanRecord.getManufacturerSpecificData(0x004c)?.let { manufacturerData ->
From 0e6cf736b436d123d02acd0c6f24b4ea4ba8a8c7 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 24 Mar 2024 13:07:44 +0100
Subject: [PATCH 115/154] manual scan keeps pause state while viewing scan
items. Limit notifyDataSetChanged to at most once per second.
---
.../ui/scan/ScanFragment.kt | 49 ++++++++++++++-----
.../ui/scan/ScanViewModel.kt | 4 +-
2 files changed, 39 insertions(+), 14 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
index 30767cc3..8fca7c7e 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
@@ -1,5 +1,6 @@
package de.seemoo.at_tracking_detection.ui.scan
+import android.annotation.SuppressLint
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.os.Bundle
@@ -24,12 +25,29 @@ import timber.log.Timber
@AndroidEntryPoint
class ScanFragment : Fragment() {
-
private val scanViewModel: ScanViewModel by viewModels()
private val bluetoothDeviceAdapterHighRisk = BluetoothDeviceAdapter()
private val bluetoothDeviceAdapterLowRisk = BluetoothDeviceAdapter()
+ private val handler = Handler(Looper.getMainLooper())
+ private var isNotifyDataSetChangedScheduled = false
+
+ @SuppressLint("NotifyDataSetChanged")
+ private val notifyDataSetChangedRunnable = Runnable {
+ // notifyDataSetChanged is very inefficient. DiffUtil does not work properly for some reason
+ bluetoothDeviceAdapterHighRisk.notifyDataSetChanged()
+ bluetoothDeviceAdapterLowRisk.notifyDataSetChanged()
+ isNotifyDataSetChangedScheduled = false
+ }
+
+ private fun scheduleNotifyDataSetChanged() {
+ if (!isNotifyDataSetChangedScheduled) {
+ handler.postDelayed(notifyDataSetChangedRunnable, 1000) // 1000 milliseconds = 1 second
+ isNotifyDataSetChangedScheduled = true
+ }
+ }
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -43,15 +61,14 @@ class ScanFragment : Fragment() {
binding.lifecycleOwner = viewLifecycleOwner
binding.vm = scanViewModel
- // TODO: notifyDataSetChanged is very inefficient. DiffUtil does not work properly for some reason
- scanViewModel.bluetoothDeviceListHighRisk.observe(viewLifecycleOwner) {
- bluetoothDeviceAdapterHighRisk.submitList(it)
- bluetoothDeviceAdapterHighRisk.notifyDataSetChanged()
+ scanViewModel.bluetoothDeviceListHighRisk.observe(viewLifecycleOwner) {newList ->
+ bluetoothDeviceAdapterHighRisk.submitList(newList)
+ scheduleNotifyDataSetChanged()
}
- scanViewModel.bluetoothDeviceListLowRisk.observe(viewLifecycleOwner) {
- bluetoothDeviceAdapterLowRisk.submitList(it)
- bluetoothDeviceAdapterLowRisk.notifyDataSetChanged()
+ scanViewModel.bluetoothDeviceListLowRisk.observe(viewLifecycleOwner) {newList ->
+ bluetoothDeviceAdapterLowRisk.submitList(newList)
+ scheduleNotifyDataSetChanged()
}
scanViewModel.scanFinished.observe(viewLifecycleOwner) {
@@ -102,12 +119,20 @@ class ScanFragment : Fragment() {
}
+ scanViewModel.scanFinished.observe(viewLifecycleOwner) {scanFinished ->
+ if (scanFinished) {
+ stopBluetoothScan()
+ } else {
+ startBluetoothScan()
+ }
+ }
+
val startStopButton = view.findViewById(R.id.button_start_stop_scan)
startStopButton.setOnClickListener {
if (scanViewModel.scanFinished.value == true) {
- startBluetoothScan()
+ scanViewModel.scanFinished.postValue(false)
} else {
- stopBluetoothScan()
+ scanViewModel.scanFinished.postValue(true)
}
}
@@ -207,7 +232,9 @@ class ScanFragment : Fragment() {
override fun onPause() {
super.onPause()
- stopBluetoothScan()
+ if (scanViewModel.scanFinished.value == false) {
+ stopBluetoothScan()
+ }
}
override fun onDestroyView() {
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 7c87e137..bc5b7ae0 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
@@ -42,8 +42,6 @@ class ScanViewModel @Inject constructor(
val sortingOrder = MutableLiveData(SortingOrder.SIGNAL_STRENGTH)
- val scanStart = MutableLiveData(LocalDateTime.MIN)
-
var bluetoothEnabled = MutableLiveData(true)
init {
bluetoothDeviceListHighRisk.value = ArrayList()
@@ -89,7 +87,7 @@ class ScanViewModel @Inject constructor(
}
}
- val deviceRepository = ATTrackingDetectionApplication.getCurrentApp()?.deviceRepository ?: return
+ val deviceRepository = ATTrackingDetectionApplication.getCurrentApp().deviceRepository
val device = deviceRepository.getDevice(uniqueIdentifier)
val bluetoothDeviceListHighRiskValue = bluetoothDeviceListHighRisk.value ?: return
From cbf96554fa03d2557e1768ecb44719e9146eaade Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 30 Mar 2024 15:22:15 +0100
Subject: [PATCH 116/154] device name can be edited from device detail view by
clicking on the device name
---
.../ui/tracking/TrackingFragment.kt | 27 +++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index e2030378..3e60a01f 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -9,6 +9,7 @@ import android.transition.TransitionInflater
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.EditText
import android.widget.ImageButton
import android.widget.TextView
import androidx.activity.addCallback
@@ -21,6 +22,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.navigation.NavDirections
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
@@ -203,6 +205,31 @@ class TrackingFragment : Fragment() {
}
addInteractions(view)
+
+ val deviceNameTextView = view.findViewById(R.id.device_name)
+
+ deviceNameTextView.setOnClickListener {
+ val device = trackingViewModel.device.value
+ if (device != null) {
+ val editName = EditText(context)
+ editName.setText(device.getDeviceNameWithID())
+ MaterialAlertDialogBuilder(requireContext())
+ .setIcon(R.drawable.ic_baseline_edit_24)
+ .setTitle(getString(R.string.devices_edit_title)).setView(editName)
+ .setNegativeButton(getString(R.string.cancel_button), null)
+ .setPositiveButton(R.string.ok_button) { _, _ ->
+ val newName = editName.text.toString()
+ device.name = newName
+ lifecycleScope.launch {
+ val deviceRepository = ATTrackingDetectionApplication.getCurrentApp().deviceRepository
+ deviceRepository.update(device)
+ Timber.d("Renamed device to ${device.name}")
+ }
+ deviceNameTextView.text = newName
+ }
+ .show()
+ }
+ }
}
private fun zoomToMarkers() {
From 3112824f97e2acf24ac6100984c002b26a3cefee Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 30 Mar 2024 16:22:29 +0100
Subject: [PATCH 117/154] limit device name to 255 characters and one line,
device name cannot be empty
---
.../ui/devices/DevicesFragment.kt | 20 ++++++++++---
.../ui/tracking/TrackingFragment.kt | 29 ++++++++++++++-----
app/src/main/res/values-de/strings.xml | 2 +-
app/src/main/res/values/strings.xml | 1 +
4 files changed, 39 insertions(+), 13 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DevicesFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DevicesFragment.kt
index e72ba72c..19d947be 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DevicesFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/devices/DevicesFragment.kt
@@ -4,12 +4,14 @@ import android.graphics.Canvas
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
+import android.text.InputFilter
import android.transition.TransitionInflater
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
+import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.doOnPreDraw
@@ -32,6 +34,7 @@ import de.seemoo.at_tracking_detection.ui.devices.filter.models.DeviceTypeFilter
import de.seemoo.at_tracking_detection.ui.devices.filter.models.IgnoredFilter
import de.seemoo.at_tracking_detection.ui.devices.filter.models.NotifiedFilter
import de.seemoo.at_tracking_detection.ui.devices.filter.models.DateRangeFilter
+import de.seemoo.at_tracking_detection.ui.tracking.TrackingFragment
import de.seemoo.at_tracking_detection.util.risk.RiskLevelEvaluator
import timber.log.Timber
import java.time.LocalDate
@@ -271,16 +274,25 @@ abstract class DevicesFragment(
val device =
deviceAdapter.currentList[viewHolder.bindingAdapterPosition]
if (direction == ItemTouchHelper.LEFT) {
- val editName = EditText(context)
+ val editName = EditText(context).apply {
+ maxLines = 1
+ filters = arrayOf(InputFilter.LengthFilter(TrackingFragment.MAX_CHARACTER_LIMIT))
+ setText(device.getDeviceNameWithID())
+ }
editName.setText(device.getDeviceNameWithID())
MaterialAlertDialogBuilder(requireContext())
.setIcon(R.drawable.ic_baseline_edit_24)
.setTitle(getString(R.string.devices_edit_title)).setView(editName)
.setNegativeButton(getString(R.string.cancel_button), null)
.setPositiveButton(R.string.ok_button) { _, _ ->
- device.name = editName.text.toString()
- devicesViewModel.update(device)
- Timber.d("Renamed device to ${device.name}")
+ val newName = editName.text.toString()
+ if (newName.isNotEmpty()) {
+ device.name = newName
+ devicesViewModel.update(device)
+ Timber.d("Renamed device to ${device.name}")
+ } else {
+ Toast.makeText(context, R.string.device_name_cannot_be_empty, Toast.LENGTH_SHORT).show()
+ }
}
.setOnDismissListener {
deviceAdapter.notifyItemChanged(viewHolder.bindingAdapterPosition)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 3e60a01f..3f4cedb5 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -5,6 +5,7 @@ import android.content.*
import android.os.Build
import android.os.Bundle
import android.os.IBinder
+import android.text.InputFilter
import android.transition.TransitionInflater
import android.view.LayoutInflater
import android.view.View
@@ -12,6 +13,7 @@ import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageButton
import android.widget.TextView
+import android.widget.Toast
import androidx.activity.addCallback
import androidx.cardview.widget.CardView
import androidx.databinding.DataBindingUtil
@@ -211,21 +213,28 @@ class TrackingFragment : Fragment() {
deviceNameTextView.setOnClickListener {
val device = trackingViewModel.device.value
if (device != null) {
- val editName = EditText(context)
- editName.setText(device.getDeviceNameWithID())
+ val editName = EditText(context).apply {
+ maxLines = 1
+ filters = arrayOf(InputFilter.LengthFilter(MAX_CHARACTER_LIMIT))
+ setText(device.getDeviceNameWithID())
+ }
MaterialAlertDialogBuilder(requireContext())
.setIcon(R.drawable.ic_baseline_edit_24)
.setTitle(getString(R.string.devices_edit_title)).setView(editName)
.setNegativeButton(getString(R.string.cancel_button), null)
.setPositiveButton(R.string.ok_button) { _, _ ->
val newName = editName.text.toString()
- device.name = newName
- lifecycleScope.launch {
- val deviceRepository = ATTrackingDetectionApplication.getCurrentApp().deviceRepository
- deviceRepository.update(device)
- Timber.d("Renamed device to ${device.name}")
+ if (newName.isNotEmpty()) {
+ device.name = newName
+ lifecycleScope.launch {
+ val deviceRepository = ATTrackingDetectionApplication.getCurrentApp().deviceRepository
+ deviceRepository.update(device)
+ Timber.d("Renamed device to ${device.name}")
+ }
+ deviceNameTextView.text = newName
+ } else {
+ Toast.makeText(context, R.string.device_name_cannot_be_empty, Toast.LENGTH_SHORT).show()
}
- deviceNameTextView.text = newName
}
.show()
}
@@ -356,4 +365,8 @@ class TrackingFragment : Fragment() {
trackingViewModel.connecting.postValue(false)
}
}
+
+ companion object {
+ const val MAX_CHARACTER_LIMIT = 255
+ }
}
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 2e76e945..69ad1d4d 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -346,7 +346,7 @@
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.
Zeigt Erklärtext
-
+ Der Name darf nicht leer sein
Mögliche Tracker
Sichere Tracker
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 57a06626..24c06a2b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -377,6 +377,7 @@
Please note:\nChipolo devices do not change their identifier.\nWhile this allows for a better tracking detection, this can also lead to false warnings.\nIn case of a tracking warning, please check if the device is not carried by someone around you (friends, family, colleges, neighbours etc.).
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.
+ The device name cannot be empty
Copyright
Developer: %s
From 14ee5a1a005c877c7ee3e5f0a032d4c9f23d654a Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 8 Apr 2024 15:44:43 +0200
Subject: [PATCH 118/154] revert savedConnections only to for Tracking
Detection relevant
---
.../database/models/device/DeviceManager.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 ccef4ff5..60a0d778 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
@@ -12,8 +12,8 @@ object DeviceManager {
val devices = listOf(AirTag, FindMy, AirPods, AppleDevice, SmartTag, SmartTagPlus, Tile, Chipolo)
private val appleDevices = listOf(AirTag, FindMy, AirPods, AppleDevice)
- val savedConnectionStates = enumValues().toList()
val unsafeConnectionState = listOf(ConnectionState.OVERMATURE_OFFLINE, ConnectionState.UNKNOWN)
+ val savedConnectionStates = unsafeConnectionState //enumValues().toList()
private val deviceTypeCache = mutableMapOf()
From 52ed100106d9cf0635f252af083667be9119784c Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 9 Apr 2024 17:43:52 +0200
Subject: [PATCH 119/154] make corners of minimap rounded
---
.../at_tracking_detection/util/ble/BLEScanner.kt | 2 +-
app/src/main/res/layout/include_map.xml | 14 ++++++++++----
2 files changed, 11 insertions(+), 5 deletions(-)
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 8c747e56..469ac160 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
@@ -92,7 +92,7 @@ object BLEScanner {
fun unregisterCallback(callback: ScanCallback) {
callbacks.remove(callback)
- Timber.d("BLE ScanCallback unregistered")
+ // Timber.d("BLE ScanCallback unregistered")
}
private var ownScanCallback = object: ScanCallback() {
diff --git a/app/src/main/res/layout/include_map.xml b/app/src/main/res/layout/include_map.xml
index 39bae1e9..54b0465d 100644
--- a/app/src/main/res/layout/include_map.xml
+++ b/app/src/main/res/layout/include_map.xml
@@ -28,15 +28,21 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
-
+ app:layout_constraintTop_toTopOf="parent">
+
+
+
\ No newline at end of file
From 34444c01aed3050566d95092f2528afecc69ee7a Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 9 Apr 2024 18:08:21 +0200
Subject: [PATCH 120/154] add (currently not visible) simpler design for device
view
---
app/src/main/res/layout/fragment_tracking.xml | 54 ++++++++++++++++++-
1 file changed, 52 insertions(+), 2 deletions(-)
diff --git a/app/src/main/res/layout/fragment_tracking.xml b/app/src/main/res/layout/fragment_tracking.xml
index a38e5117..6515a968 100644
--- a/app/src/main/res/layout/fragment_tracking.xml
+++ b/app/src/main/res/layout/fragment_tracking.xml
@@ -24,6 +24,56 @@
android:layout_height="wrap_content"
tools:context=".ui.tracking.TrackingFragment">
+
+
+
+
+
+
+
+
+
+
+
+
+
Date: Sat, 13 Apr 2024 15:26:02 +0200
Subject: [PATCH 121/154] altitude of each location now gets saved
---
.../16.json | 489 ++++++++++++++++++
.../database/AppDatabase.kt | 5 +-
.../database/models/Location.kt | 3 +
.../detection/BackgroundBluetoothScanner.kt | 80 +--
.../detection/LocationProvider.kt | 6 +-
.../ui/scan/ScanViewModel.kt | 1 +
6 files changed, 545 insertions(+), 39 deletions(-)
create mode 100644 app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/16.json
diff --git a/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/16.json b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/16.json
new file mode 100644
index 00000000..dadd4cb9
--- /dev/null
+++ b/app/schemas/de.seemoo.at_tracking_detection.database.AppDatabase/16.json
@@ -0,0 +1,489 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 16,
+ "identityHash": "ae2d890772bfe46cb4203a3676128dc2",
+ "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, `riskLevel` INTEGER NOT NULL DEFAULT 0, `lastCalculatedRiskDate` TEXT, `nextObservationNotification` TEXT, `currentObservationDuration` INTEGER, `safeTracker` INTEGER NOT NULL DEFAULT false)",
+ "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
+ },
+ {
+ "fieldPath": "riskLevel",
+ "columnName": "riskLevel",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "lastCalculatedRiskDate",
+ "columnName": "lastCalculatedRiskDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextObservationNotification",
+ "columnName": "nextObservationNotification",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "currentObservationDuration",
+ "columnName": "currentObservationDuration",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "safeTracker",
+ "columnName": "safeTracker",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "false"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "deviceId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_device_lastSeen",
+ "unique": false,
+ "columnNames": [
+ "lastSeen"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_device_lastSeen` ON `${TABLE_NAME}` (`lastSeen`)"
+ },
+ {
+ "name": "index_device_address",
+ "unique": true,
+ "columnNames": [
+ "address"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_device_address` ON `${TABLE_NAME}` (`address`)"
+ },
+ {
+ "name": "index_device_notificationSent",
+ "unique": false,
+ "columnNames": [
+ "notificationSent"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_device_notificationSent` ON `${TABLE_NAME}` (`notificationSent`)"
+ },
+ {
+ "name": "index_device_deviceType",
+ "unique": false,
+ "columnNames": [
+ "deviceType"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_device_deviceType` ON `${TABLE_NAME}` (`deviceType`)"
+ },
+ {
+ "name": "index_device_lastSeen_deviceType",
+ "unique": false,
+ "columnNames": [
+ "lastSeen",
+ "deviceType"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_device_lastSeen_deviceType` ON `${TABLE_NAME}` (`lastSeen`, `deviceType`)"
+ }
+ ],
+ "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, `sensitivity` INTEGER NOT NULL DEFAULT 0)",
+ "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
+ },
+ {
+ "fieldPath": "sensitivity",
+ "columnName": "sensitivity",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "notificationId"
+ ]
+ },
+ "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, `locationId` INTEGER, `mfg` BLOB, `serviceUUIDs` TEXT, `connectionState` TEXT NOT NULL DEFAULT 'UNKNOWN')",
+ "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": "locationId",
+ "columnName": "locationId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "manufacturerData",
+ "columnName": "mfg",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serviceUUIDs",
+ "columnName": "serviceUUIDs",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "connectionState",
+ "columnName": "connectionState",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'UNKNOWN'"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "beaconId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_beacon_receivedAt",
+ "unique": false,
+ "columnNames": [
+ "receivedAt"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_beacon_receivedAt` ON `${TABLE_NAME}` (`receivedAt`)"
+ },
+ {
+ "name": "index_beacon_deviceAddress",
+ "unique": false,
+ "columnNames": [
+ "deviceAddress"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_beacon_deviceAddress` ON `${TABLE_NAME}` (`deviceAddress`)"
+ },
+ {
+ "name": "index_beacon_connectionState",
+ "unique": false,
+ "columnNames": [
+ "connectionState"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_beacon_connectionState` ON `${TABLE_NAME}` (`connectionState`)"
+ }
+ ],
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "feedbackId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "scan",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scanId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `endDate` TEXT, `noDevicesFound` INTEGER, `duration` INTEGER, `isManual` INTEGER NOT NULL, `scanMode` INTEGER NOT NULL, `startDate` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "scanId",
+ "columnName": "scanId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endDate",
+ "columnName": "endDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "noDevicesFound",
+ "columnName": "noDevicesFound",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "duration",
+ "columnName": "duration",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isManual",
+ "columnName": "isManual",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "scanMode",
+ "columnName": "scanMode",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startDate",
+ "columnName": "startDate",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "scanId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "location",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`locationId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `firstDiscovery` TEXT NOT NULL, `lastSeen` TEXT NOT NULL, `longitude` REAL NOT NULL, `latitude` REAL NOT NULL, `altitude` REAL, `accuracy` REAL)",
+ "fields": [
+ {
+ "fieldPath": "locationId",
+ "columnName": "locationId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstDiscovery",
+ "columnName": "firstDiscovery",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastSeen",
+ "columnName": "lastSeen",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longitude",
+ "columnName": "longitude",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latitude",
+ "columnName": "latitude",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "altitude",
+ "columnName": "altitude",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "accuracy",
+ "columnName": "accuracy",
+ "affinity": "REAL",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "locationId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_location_latitude_longitude",
+ "unique": true,
+ "columnNames": [
+ "latitude",
+ "longitude"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_location_latitude_longitude` ON `${TABLE_NAME}` (`latitude`, `longitude`)"
+ }
+ ],
+ "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, 'ae2d890772bfe46cb4203a3676128dc2')"
+ ]
+ }
+}
\ No newline at end of file
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 0bdab6b3..ebc7d78f 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
@@ -8,7 +8,7 @@ import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.util.converter.DateTimeConverter
@Database(
- version = 15,
+ version = 16,
entities = [
BaseDevice::class,
Notification::class,
@@ -28,7 +28,8 @@ import de.seemoo.at_tracking_detection.util.converter.DateTimeConverter
AutoMigration(from=11, to=12),
AutoMigration(from=12, to=13),
AutoMigration(from=13, to=14),
- AutoMigration(from=14, to=15)
+ AutoMigration(from=14, to=15),
+ AutoMigration(from=15, to=16)
],
exportSchema = true
)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Location.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Location.kt
index 60c0e8f7..5d38bc4e 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Location.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Location.kt
@@ -11,12 +11,14 @@ data class Location(
@ColumnInfo(name = "lastSeen") var lastSeen: LocalDateTime,
@ColumnInfo(name = "longitude") var longitude: Double,
@ColumnInfo(name = "latitude") var latitude: Double,
+ @ColumnInfo(name = "altitude") var altitude: Double?,
@ColumnInfo(name = "accuracy") var accuracy: Float?,
) {
constructor(
firstDiscovery: LocalDateTime,
longitude: Double,
latitude: Double,
+ altitude: Double?,
accuracy: Float?
): this(
0,
@@ -25,6 +27,7 @@ data class Location(
firstDiscovery, // lastSeen
longitude,
latitude,
+ altitude,
accuracy
)
}
\ No newline at end of file
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 4500b14b..f0e7b267 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
@@ -6,12 +6,9 @@ import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
-import android.content.SharedPreferences
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
-import androidx.work.Data
-import androidx.work.ListenableWorker
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.BuildConfig
import de.seemoo.at_tracking_detection.database.models.Beacon
@@ -31,8 +28,6 @@ import kotlinx.coroutines.delay
import timber.log.Timber
import java.time.LocalDateTime
import java.util.concurrent.ConcurrentHashMap
-import javax.inject.Inject
-import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@@ -55,26 +50,26 @@ object BackgroundBluetoothScanner {
private var locationFetchStarted: Long? = null
- val backgroundWorkScheduler: BackgroundWorkScheduler?
+ val backgroundWorkScheduler: BackgroundWorkScheduler
get() {
- return ATTrackingDetectionApplication.getCurrentApp()?.backgroundWorkScheduler
+ return ATTrackingDetectionApplication.getCurrentApp().backgroundWorkScheduler
}
- val notificationService: NotificationService?
+ val notificationService: NotificationService
get() {
- return ATTrackingDetectionApplication.getCurrentApp()?.notificationService
+ return ATTrackingDetectionApplication.getCurrentApp().notificationService
}
- val locationProvider: LocationProvider?
+ val locationProvider: LocationProvider
get() {
- return ATTrackingDetectionApplication.getCurrentApp()?.locationProvider
+ return ATTrackingDetectionApplication.getCurrentApp().locationProvider
}
- val scanRepository: ScanRepository?
+ val scanRepository: ScanRepository
get() {
- return ATTrackingDetectionApplication.getCurrentApp()?.scanRepository
+ return ATTrackingDetectionApplication.getCurrentApp().scanRepository
}
- public var isScanning = false
+ var isScanning = false
class BackgroundScanResults(var duration: Long, var scanMode: Int, var numberDevicesFound: Int, var failed: Boolean)
suspend fun scanInBackground(startedFrom: String): BackgroundScanResults {
if (isScanning) {
@@ -84,7 +79,8 @@ object BackgroundBluetoothScanner {
Timber.d("Starting BackgroundBluetoothScanner from $startedFrom")
val scanMode = getScanMode()
- val scanId = scanRepository?.insert(Scan(startDate = LocalDateTime.now(), isManual = false, scanMode = scanMode))
+ val scanId =
+ scanRepository.insert(Scan(startDate = LocalDateTime.now(), isManual = false, scanMode = scanMode))
if (!Utility.checkBluetoothPermission()) {
@@ -116,7 +112,7 @@ object BackgroundBluetoothScanner {
if (useLocation) {
// Returns the last known location if this matches our requirements or starts new location updates
locationFetchStarted = System.currentTimeMillis()
- location = locationProvider?.lastKnownOrRequestLocationUpdates(locationRequester = locationRequester, timeoutMillis = BackgroundBluetoothScanner.LOCATION_UPDATE_MAX_TIME_MS - 2000L)
+ location = locationProvider.lastKnownOrRequestLocationUpdates(locationRequester = locationRequester, timeoutMillis = LOCATION_UPDATE_MAX_TIME_MS - 2000L)
}
//Starting BLE Scan
@@ -140,7 +136,7 @@ object BackgroundBluetoothScanner {
Timber.d("Fetched location? $fetchedLocation")
if (location == null) {
// Get the last location no matter if the requirements match or not
- location = locationProvider?.getLastLocation(checkRequirements = false)
+ location = locationProvider.getLastLocation(checkRequirements = false)
}
val validDeviceTypes = DeviceType.getAllowedDeviceTypesFromSettings()
@@ -150,12 +146,13 @@ object BackgroundBluetoothScanner {
val deviceType = DeviceManager.getDeviceType(discoveredDevice.scanResult)
if (deviceType in validDeviceTypes) {
- BackgroundBluetoothScanner.insertScanResult(
- discoveredDevice.scanResult,
- location?.latitude,
- location?.longitude,
- location?.accuracy,
- discoveredDevice.discoveryDate,
+ insertScanResult(
+ scanResult = discoveredDevice.scanResult,
+ latitude = location?.latitude,
+ longitude = location?.longitude,
+ altitude = location?.altitude,
+ accuracy = location?.accuracy,
+ discoveryDate = discoveredDevice.discoveryDate,
)
}
}
@@ -163,17 +160,17 @@ object BackgroundBluetoothScanner {
SharedPrefs.lastScanDate = LocalDateTime.now()
SharedPrefs.isScanningInBackground = false
if (scanId != null) {
- val scan = scanRepository?.scanWithId(scanId.toInt())
+ val scan = scanRepository.scanWithId(scanId.toInt())
if (scan != null) {
scan.endDate = LocalDateTime.now()
scan.duration = scanDuration.toInt() / 1000
scan.noDevicesFound = scanResultDictionary.size
- scanRepository?.update(scan)
+ scanRepository.update(scan)
}
}
Timber.d("Scheduling tracking detector worker")
- backgroundWorkScheduler?.scheduleTrackingDetector()
+ backgroundWorkScheduler.scheduleTrackingDetector()
BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
// Release the wake lock when we are done
@@ -203,7 +200,7 @@ object BackgroundBluetoothScanner {
super.onScanFailed(errorCode)
Timber.e("Bluetooth scan failed $errorCode")
if (BuildConfig.DEBUG) {
- notificationService?.sendBLEErrorNotification()
+ notificationService.sendBLEErrorNotification()
}
}
}
@@ -263,7 +260,7 @@ object BackgroundBluetoothScanner {
}
// Fallback if no location is fetched in time
- val maximumLocationDurationMillis = BackgroundBluetoothScanner.LOCATION_UPDATE_MAX_TIME_MS
+ val maximumLocationDurationMillis = LOCATION_UPDATE_MAX_TIME_MS
handler.postDelayed(runnable, maximumLocationDurationMillis)
}
}
@@ -280,6 +277,7 @@ object BackgroundBluetoothScanner {
scanResult: ScanResult,
latitude: Double?,
longitude: Double?,
+ altitude: Double?,
accuracy: Float?,
discoveryDate: LocalDateTime,
): Pair {
@@ -289,7 +287,13 @@ object BackgroundBluetoothScanner {
) // return when device does not qualify to be saved
// set locationId to null if gps location could not be retrieved
- val locId: Int? = saveLocation(latitude, longitude, discoveryDate, accuracy)?.locationId
+ val locId: Int? = saveLocation(
+ latitude = latitude,
+ longitude = longitude,
+ altitude = altitude,
+ discoveryDate = discoveryDate,
+ accuracy = accuracy
+ )?.locationId
val beaconSaved =
saveBeacon(scanResult, discoveryDate, locId) ?: return Pair(null, null)
@@ -303,7 +307,7 @@ object BackgroundBluetoothScanner {
locId: Int?
): Beacon? {
val beaconRepository =
- ATTrackingDetectionApplication.getCurrentApp()?.beaconRepository ?: return null
+ ATTrackingDetectionApplication.getCurrentApp().beaconRepository
val uuids = scanResult.scanRecord?.serviceUuids?.map { it.toString() }?.toList()
val uniqueIdentifier = BaseDevice.getPublicKey(scanResult)
@@ -352,7 +356,7 @@ object BackgroundBluetoothScanner {
discoveryDate: LocalDateTime
): BaseDevice? {
val deviceRepository =
- ATTrackingDetectionApplication.getCurrentApp()?.deviceRepository ?: return null
+ ATTrackingDetectionApplication.getCurrentApp().deviceRepository
val deviceAddress = BaseDevice.getPublicKey(scanResult)
@@ -389,11 +393,12 @@ object BackgroundBluetoothScanner {
private suspend fun saveLocation(
latitude: Double?,
longitude: Double?,
+ altitude: Double?,
discoveryDate: LocalDateTime,
accuracy: Float?
): Location? {
val locationRepository =
- ATTrackingDetectionApplication.getCurrentApp()?.locationRepository ?: return null
+ ATTrackingDetectionApplication.getCurrentApp().locationRepository
// set location to null if gps location could not be retrieved
var location: Location? = null
@@ -414,9 +419,16 @@ object BackgroundBluetoothScanner {
if (location == null || distanceBetweenLocations > MAX_DISTANCE_UNTIL_NEW_LOCATION) {
// Create new location entry
Timber.d("Add new Location to the database!")
- location = Location(discoveryDate, longitude, latitude, accuracy)
+ location = Location(
+ firstDiscovery = discoveryDate,
+ longitude = longitude,
+ latitude = latitude,
+ altitude = altitude,
+ accuracy = accuracy,
+ )
locationRepository.insert(location)
- } else {
+ }
+ else {
// If location is within the set limit, just use that location and update lastSeen
Timber.d("Location already in the database... Updating the last seen date!")
location.lastSeen = discoveryDate
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 d8087f10..d414a383 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
@@ -5,12 +5,12 @@ import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
-import android.os.*
+import android.os.Handler
+import android.os.Looper
import androidx.core.content.ContextCompat
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import timber.log.Timber
-import java.time.LocalDateTime
-import java.util.*
+import java.util.Date
import javax.inject.Inject
import javax.inject.Singleton
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 bc5b7ae0..57c1bf90 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
@@ -81,6 +81,7 @@ class ScanViewModel @Inject constructor(
scanResult = scanResult,
latitude = location?.latitude,
longitude = location?.longitude,
+ altitude = location?.altitude,
accuracy = location?.accuracy,
discoveryDate = currentDate,
)
From d9bdf5d552c0ca445cd559c842146362fcc3b52f Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sat, 13 Apr 2024 15:42:31 +0200
Subject: [PATCH 122/154] update existing locations if more recent and more
accurate data is available
---
.../database/models/Location.kt | 16 ++++++++--------
.../detection/BackgroundBluetoothScanner.kt | 9 ++++++++-
2 files changed, 16 insertions(+), 9 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Location.kt b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Location.kt
index 5d38bc4e..21a27ce4 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Location.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/database/models/Location.kt
@@ -21,13 +21,13 @@ data class Location(
altitude: Double?,
accuracy: Float?
): this(
- 0,
- null,
- firstDiscovery, // firstDiscovery
- firstDiscovery, // lastSeen
- longitude,
- latitude,
- altitude,
- accuracy
+ locationId = 0,
+ name = null,
+ firstDiscovery = firstDiscovery,
+ lastSeen = firstDiscovery,
+ longitude = longitude,
+ latitude = latitude,
+ altitude = altitude,
+ accuracy = accuracy,
)
}
\ No newline at end of file
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 f0e7b267..2bf32021 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
@@ -432,8 +432,15 @@ object BackgroundBluetoothScanner {
// If location is within the set limit, just use that location and update lastSeen
Timber.d("Location already in the database... Updating the last seen date!")
location.lastSeen = discoveryDate
+ if (altitude != null) {
+ location.altitude = altitude
+ }
+ if (accuracy != null && (location.accuracy == null || location.accuracy!! > accuracy)) {
+ location.accuracy = accuracy
+ location.longitude = longitude
+ location.latitude = latitude
+ }
locationRepository.update(location)
-
}
Timber.d("Location: $location")
From 515f9a3e54762b98ab780c5416161d6a04d06f82 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 15 Apr 2024 17:28:53 +0200
Subject: [PATCH 123/154] upgrade gradle and some dependecies
---
app/build.gradle | 8 ++++----
build.gradle | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index f45fc221..4055b799 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -160,15 +160,15 @@ dependencies {
// Integration with activities
implementation 'androidx.activity:activity-compose:1.8.2'
// Compose Material Design
- implementation 'androidx.compose.material:material:1.6.4'
+ implementation 'androidx.compose.material:material:1.6.5'
// Animations
- implementation 'androidx.compose.animation:animation:1.6.4'
+ implementation 'androidx.compose.animation:animation:1.6.5'
// Tooling support (Previews, etc.)
- implementation 'androidx.compose.ui:ui-tooling:1.6.4'
+ implementation 'androidx.compose.ui:ui-tooling:1.6.5'
// Integration with ViewModels
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
// UI Tests
- androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.4'
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.5'
// When using a MDC theme
implementation "com.google.android.material:compose-theme-adapter:1.2.1"
diff --git a/build.gradle b/build.gradle
index 97be67b0..a4a6b781 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@ buildscript {
}
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.3.1'
+ classpath 'com.android.tools.build:gradle:8.3.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7"
From 0aa331807b96638904e17fd4c6dfd0728b2cee43 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 15 Apr 2024 17:31:49 +0200
Subject: [PATCH 124/154] upgrade Kotlin Version
---
app/build.gradle | 2 +-
build.gradle | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 4055b799..a81c562e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -66,7 +66,7 @@ android {
}
composeOptions {
- kotlinCompilerExtensionVersion "1.5.8"
+ kotlinCompilerExtensionVersion "1.5.11"
}
diff --git a/build.gradle b/build.gradle
index a4a6b781..55dff98a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.9.22'
+ ext.kotlin_version = '1.9.23'
ext.hilt_version = '2.51'
ext.room_version = '2.6.1'
ext.compose_version = '1.1.1'
From 8300cedcc8e0ad13eef512ab8c2555b32dbe89d8 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Tue, 16 Apr 2024 15:29:18 +0200
Subject: [PATCH 125/154] do not save devices above an altitude of 9000 meters.
This should avoid unnecessary detections on planes.
---
.../detection/BackgroundBluetoothScanner.kt | 6 ++++++
.../detection/TrackingDetectorConstants.kt | 1 +
2 files changed, 7 insertions(+)
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 2bf32021..2fee02bc 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
@@ -281,6 +281,12 @@ object BackgroundBluetoothScanner {
accuracy: Float?,
discoveryDate: LocalDateTime,
): Pair {
+ if (altitude != null && altitude > TrackingDetectorConstants.IGNORE_ABOVE_ALTITUDE) {
+ Timber.d("Ignoring location above ${TrackingDetectorConstants.IGNORE_ABOVE_ALTITUDE}m, we assume the User is on a plane!")
+ // Do not save device in case we assume it is on a plane
+ return Pair(null, null)
+ }
+
val deviceSaved = saveDevice(scanResult, discoveryDate) ?: return Pair(
null,
null
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/detection/TrackingDetectorConstants.kt b/app/src/main/java/de/seemoo/at_tracking_detection/detection/TrackingDetectorConstants.kt
index 09d17997..20b7f831 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/detection/TrackingDetectorConstants.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/detection/TrackingDetectorConstants.kt
@@ -1,6 +1,7 @@
package de.seemoo.at_tracking_detection.detection
object TrackingDetectorConstants {
+ const val IGNORE_ABOVE_ALTITUDE = 9000.0
const val BLUETOOTH_DEVICE_FOUND_ACTION =
"de.seemoo.at_tracking_detection.BLUETOOTH_DEVICE_FOUND"
}
\ No newline at end of file
From dfa19756c6fe996fc4b3750b65cc7fe2a2c80f48 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Fri, 26 Apr 2024 11:40:09 +0200
Subject: [PATCH 126/154] don't create location object above 3000 meters, this
is different to not creating a device object above 9000 meters from the
previous commit
---
.../detection/BackgroundBluetoothScanner.kt | 12 +++++++++---
.../detection/TrackingDetectorConstants.kt | 3 ++-
2 files changed, 11 insertions(+), 4 deletions(-)
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 2fee02bc..1f76746f 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
@@ -281,9 +281,9 @@ object BackgroundBluetoothScanner {
accuracy: Float?,
discoveryDate: LocalDateTime,
): Pair {
- if (altitude != null && altitude > TrackingDetectorConstants.IGNORE_ABOVE_ALTITUDE) {
- Timber.d("Ignoring location above ${TrackingDetectorConstants.IGNORE_ABOVE_ALTITUDE}m, we assume the User is on a plane!")
- // Do not save device in case we assume it is on a plane
+ if (altitude != null && altitude > TrackingDetectorConstants.IGNORE_DEVICE_ABOVE_ALTITUDE) {
+ Timber.d("Ignoring device for locations above ${TrackingDetectorConstants.IGNORE_DEVICE_ABOVE_ALTITUDE}m, we assume the User is on a plane!")
+ // Do not save device at all in case we assume it is on a plane
return Pair(null, null)
}
@@ -403,6 +403,12 @@ object BackgroundBluetoothScanner {
discoveryDate: LocalDateTime,
accuracy: Float?
): Location? {
+ if (altitude != null && altitude > TrackingDetectorConstants.IGNORE_LOCATION_ABOVE_ALTITUDE) {
+ Timber.d("Ignoring location above ${TrackingDetectorConstants.IGNORE_LOCATION_ABOVE_ALTITUDE}m, we assume the User might be on a plane!")
+ // Do not save location object
+ return null
+ }
+
val locationRepository =
ATTrackingDetectionApplication.getCurrentApp().locationRepository
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/detection/TrackingDetectorConstants.kt b/app/src/main/java/de/seemoo/at_tracking_detection/detection/TrackingDetectorConstants.kt
index 20b7f831..48abf60b 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/detection/TrackingDetectorConstants.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/detection/TrackingDetectorConstants.kt
@@ -1,7 +1,8 @@
package de.seemoo.at_tracking_detection.detection
object TrackingDetectorConstants {
- const val IGNORE_ABOVE_ALTITUDE = 9000.0
+ const val IGNORE_DEVICE_ABOVE_ALTITUDE = 9000.0
+ const val IGNORE_LOCATION_ABOVE_ALTITUDE = 3000.0
const val BLUETOOTH_DEVICE_FOUND_ACTION =
"de.seemoo.at_tracking_detection.BLUETOOTH_DEVICE_FOUND"
}
\ No newline at end of file
From cefded68bdcb4442879f4261a330d03a8775bb8b Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 2 May 2024 10:24:05 +0200
Subject: [PATCH 127/154] upgrade gradle to 8.4.0
---
build.gradle | 2 +-
gradle/wrapper/gradle-wrapper.properties | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/build.gradle b/build.gradle
index 55dff98a..efddf3f4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@ buildscript {
}
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.3.2'
+ classpath 'com.android.tools.build:gradle:8.4.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 6b85a69d..31dcac63 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Wed Aug 02 18:45:44 CEST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
From 9fff0da37d971a8676076341d67b1bddcc3e4a85 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 2 May 2024 10:28:42 +0200
Subject: [PATCH 128/154] fix bug where devices where not correctly put into
safe and unsafe trackers
---
.../de/seemoo/at_tracking_detection/ui/scan/ScanViewModel.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 57c1bf90..3b5e0b36 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
@@ -101,7 +101,7 @@ class ScanViewModel @Inject constructor(
getPublicKey(it) == uniqueIdentifier
}
- if (BaseDevice.getConnectionState(scanResult) in DeviceManager.unsafeConnectionState && (device != null && !device.ignore) || device == null) {
+ if (BaseDevice.getConnectionState(scanResult) in DeviceManager.unsafeConnectionState && ((device != null && !device.ignore) || device==null)) {
// only add possible devices to list
bluetoothDeviceListHighRiskValue.add(scanResult)
} else {
From d5d0fba331754647b0a5c921ddf4ff9e00c15275 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 2 May 2024 10:41:32 +0200
Subject: [PATCH 129/154] make some string more understandable
---
app/src/main/res/values-de/strings.xml | 12 ++++++------
app/src/main/res/values/strings.xml | 12 ++++++------
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 69ad1d4d..1615cf82 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -8,7 +8,7 @@
Feedback
Gerät mit der ID {deviceAddress} folgt Dir
Gerät mit ID: {deviceAddress}
- Detail Scan: {deviceAddress}
+ Orten: {deviceAddress}
Scan
Alle Geräte
Geräte
@@ -39,7 +39,7 @@
OK
Feedback
Mehr Informationen mitteilen
- Detaillierter Scan
+ Gerät orten
Zeigt Live Informationen über diesen Tracker
Gerät ignorieren
Benachrichtigungen für dieses Gerät deaktivieren
@@ -339,10 +339,10 @@
Entwickler: %s
Maintainer: %s
- Achtung:\nApple Geräte ändern ihre Kennung alle 24 Stunden nachdem sie einige Zeit vom Besitzergerät getrennt sind.\nAirTags, AirPods, FindMy-Geräte und andere Apple Geräte werden jeden Tag als ein neues Gerät erkannt.
- Achtung:\nSamsung Geräte ändern ihre Kennung alle 24 Stunden nachdem sie längere Zeit vom Besitzergerät getrennt sind.\nSmartTags und andere Samsung Geräte werden jeden Tag als ein neues Gerät erkannt.
- Achtung:\nTile Geräte ändern ihre Kennung nicht.\nDies ermöglicht zwar eine genauere Tracking Erkennung, führt aber auch zu mehr falschen Warnungen.\nFalls sie über ein Tile gewarnt werden, überprüfen Sie ob der Tracker nicht von jemanden aus ihrer Umgebung (Freunde, Familie, Kollegen, Nachbarn) stammt.
- Achtung:\nChipolo Geräte ändern ihre Kennung nicht.\nDies ermöglicht zwar eine genauere Tracking Erkennung, führt aber auch zu mehr falschen Warnungen.\nFalls sie über einen Chipolo-Tracker gewarnt werden, überprüfen Sie ob der Tracker nicht von jemanden aus ihrer Umgebung (Freunde, Familie, Kollegen, Nachbarn) stammt.
+ Hinweis:\nApple Geräte ändern ihre Identität mindestens alle 24 Stunden.\nAirTags, AirPods, FindMy-Geräte und andere Apple Geräte werden daher periodisch als neue Geräte erkannt und könnten mehrfach angezeigt werden.
+ 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.
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.
Zeigt Erklärtext
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 24c06a2b..ff6c488f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -9,7 +9,7 @@
A device with this ID: {deviceAddress} is following you
Device ID: {deviceAddress}
Scan
- Detailed scan: {deviceAddress}
+ Locate: {deviceAddress}
Observe tracker: {deviceAddress}
All Devices
Devices
@@ -46,7 +46,7 @@
OK
Feedback
Provide some insights
- Detailed Scan
+ Locate Tracker
Shows live Information about this tracker
Ignore Device
Mute notifications for this device
@@ -371,10 +371,10 @@
No internet connection
You are currently not connected to the internet. You will find articles that will help you navigate the app here when you are connected to the internet.
- Please note:\nApple devices change their identifier every 24 hours once they are disconnected from their owner device for a certain time.\nAirTags, AirPods, FindMy and other Apple devices will be detected as a new device every day.
- Please note:\nSamsung devices change their identifier every 24 hours once they are disconnected from their owner device for a longer time.\nSmartTags and other Samsung SmartThings devices will be detected as a new device every day.
- Please note:\nTile devices do not change their identifier.\nWhile this allows for a better tracking detection, this can also lead to false warnings.\nIn case of a tracking 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.\nWhile this allows for a better tracking detection, this can also lead to false warnings.\nIn case of a tracking warning, please check if the device is not carried by someone around you (friends, family, colleges, neighbours etc.).
+ Please note:\nApple devices change their identifier over time.\nAirTags, AirPods, FindMy and other Apple devices are recognized as new devices periodically and can be displayed multiple times.
+ 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.).
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.
The device name cannot be empty
From 7b4b574ae68a7992e5ee7f0ade5555cdcc56bc2c Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 2 May 2024 10:45:26 +0200
Subject: [PATCH 130/154] make padding of map more consistent
---
app/src/main/res/layout/fragment_tracking.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/layout/fragment_tracking.xml b/app/src/main/res/layout/fragment_tracking.xml
index 6515a968..e02f9acd 100644
--- a/app/src/main/res/layout/fragment_tracking.xml
+++ b/app/src/main/res/layout/fragment_tracking.xml
@@ -78,6 +78,7 @@
android:id="@+id/map_container"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:layout_margin="16dp"
android:visibility="@{vm.noLocationsYet ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toTopOf="@id/tracking_tiles"
app:layout_constraintHeight_min="250dp"
From ee0717eb2511bcdcde2bacdbe464fb1b819780cb Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Mon, 13 May 2024 16:34:04 +0200
Subject: [PATCH 131/154] massive speed up of manual scan, cleanup code
---
.../ui/scan/ScanFragment.kt | 91 ++----------------
.../ui/scan/ScanViewModel.kt | 96 +++++++------------
app/src/main/res/layout/fragment_scan.xml | 81 ----------------
3 files changed, 38 insertions(+), 230 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
index 8fca7c7e..5b52b7f8 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
@@ -30,24 +30,7 @@ class ScanFragment : Fragment() {
private val bluetoothDeviceAdapterHighRisk = BluetoothDeviceAdapter()
private val bluetoothDeviceAdapterLowRisk = BluetoothDeviceAdapter()
- private val handler = Handler(Looper.getMainLooper())
- private var isNotifyDataSetChangedScheduled = false
-
@SuppressLint("NotifyDataSetChanged")
- private val notifyDataSetChangedRunnable = Runnable {
- // notifyDataSetChanged is very inefficient. DiffUtil does not work properly for some reason
- bluetoothDeviceAdapterHighRisk.notifyDataSetChanged()
- bluetoothDeviceAdapterLowRisk.notifyDataSetChanged()
- isNotifyDataSetChangedScheduled = false
- }
-
- private fun scheduleNotifyDataSetChanged() {
- if (!isNotifyDataSetChangedScheduled) {
- handler.postDelayed(notifyDataSetChangedRunnable, 1000) // 1000 milliseconds = 1 second
- isNotifyDataSetChangedScheduled = true
- }
- }
-
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -63,12 +46,12 @@ class ScanFragment : Fragment() {
scanViewModel.bluetoothDeviceListHighRisk.observe(viewLifecycleOwner) {newList ->
bluetoothDeviceAdapterHighRisk.submitList(newList)
- scheduleNotifyDataSetChanged()
+ bluetoothDeviceAdapterHighRisk.notifyDataSetChanged()
}
scanViewModel.bluetoothDeviceListLowRisk.observe(viewLifecycleOwner) {newList ->
bluetoothDeviceAdapterLowRisk.submitList(newList)
- scheduleNotifyDataSetChanged()
+ bluetoothDeviceAdapterLowRisk.notifyDataSetChanged()
}
scanViewModel.scanFinished.observe(viewLifecycleOwner) {
@@ -79,33 +62,6 @@ class ScanFragment : Fragment() {
}
}
- scanViewModel.sortingOrder.observe(viewLifecycleOwner) {
- val bluetoothDeviceListHighRiskValue = scanViewModel.bluetoothDeviceListHighRisk.value ?: return@observe
- val bluetoothDeviceListLowRiskValue = scanViewModel.bluetoothDeviceListLowRisk.value ?: return@observe
-
- scanViewModel.sortResults(bluetoothDeviceListHighRiskValue)
- scanViewModel.sortResults(bluetoothDeviceListLowRiskValue)
-
- scanViewModel.bluetoothDeviceListHighRisk.postValue(ArrayList(bluetoothDeviceListHighRiskValue))
- scanViewModel.bluetoothDeviceListLowRisk.postValue(ArrayList(bluetoothDeviceListLowRiskValue))
-
-// if (view != null) {
-// val sortBySignalStrength = requireView().findViewById(R.id.sort_option_signal_strength)
-// val sortByDetectionOrder = requireView().findViewById(R.id.sort_option_order_detection)
-// val sortByAddress = requireView().findViewById(R.id.sort_option_address)
-//
-// val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
-//
-// when(it) {
-// SortingOrder.SIGNAL_STRENGTH -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
-// SortingOrder.DETECTION_ORDER -> scanViewModel.changeColorOf(sortOptions, sortByDetectionOrder)
-// SortingOrder.ADDRESS -> scanViewModel.changeColorOf(sortOptions, sortByAddress)
-// else -> scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
-// }
-// }
-
- }
-
return binding.root
}
@@ -116,7 +72,6 @@ class ScanFragment : Fragment() {
val bluetoothButton = view.findViewById(R.id.open_ble_settings_button)
bluetoothButton.setOnClickListener {
context?.let { BLEScanner.openBluetoothSettings(it) }
-
}
scanViewModel.scanFinished.observe(viewLifecycleOwner) {scanFinished ->
@@ -140,25 +95,6 @@ class ScanFragment : Fragment() {
infoButton.setOnClickListener {
toggleInfoLayoutVisibility(view)
}
-
-
-// val sortBySignalStrength = view.findViewById(R.id.sort_option_signal_strength)
-// val sortByDetectionOrder = view.findViewById(R.id.sort_option_order_detection)
-// val sortByAddress = view.findViewById(R.id.sort_option_address)
-//
-// val sortOptions = listOf(sortBySignalStrength, sortByDetectionOrder, sortByAddress)
-//
-// scanViewModel.changeColorOf(sortOptions, sortBySignalStrength)
-//
-// sortBySignalStrength.setOnClickListener {
-// scanViewModel.sortingOrder.postValue(SortingOrder.SIGNAL_STRENGTH)
-// }
-// sortByDetectionOrder.setOnClickListener {
-// scanViewModel.sortingOrder.postValue(SortingOrder.DETECTION_ORDER)
-// }
-// sortByAddress.setOnClickListener {
-// scanViewModel.sortingOrder.postValue(SortingOrder.ADDRESS)
-// }
}
override fun onStart() {
@@ -167,10 +103,7 @@ class ScanFragment : Fragment() {
}
private fun toggleInfoLayoutVisibility(view: View) {
- // Find the info layout by its ID
val infoLayout = view.findViewById(R.id.info_layout)
-
- // Toggle the visibility
infoLayout.visibility = if (infoLayout.visibility == View.VISIBLE) View.GONE else View.VISIBLE
}
@@ -191,7 +124,7 @@ class ScanFragment : Fragment() {
it,
R.string.ble_service_connection_error,
Snackbar.LENGTH_LONG
- )
+ ).show()
}
}
}
@@ -203,7 +136,9 @@ class ScanFragment : Fragment() {
}
// Register the current fragment as a callback
- BLEScanner.registerCallback(this.scanCallback)
+ if (this.scanCallback !in BLEScanner.callbacks) {
+ BLEScanner.registerCallback(this.scanCallback)
+ }
scanViewModel.scanFinished.postValue(false)
// Show to the user that no devices have been found
@@ -223,20 +158,6 @@ class ScanFragment : Fragment() {
scanViewModel.scanFinished.postValue(true)
}
- override fun onResume() {
- super.onResume()
- if (scanViewModel.scanFinished.value == false) {
- startBluetoothScan()
- }
- }
-
- override fun onPause() {
- super.onPause()
- if (scanViewModel.scanFinished.value == false) {
- stopBluetoothScan()
- }
- }
-
override fun onDestroyView() {
super.onDestroyView()
stopBluetoothScan()
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 3b5e0b36..bc1acac0 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
@@ -1,13 +1,11 @@
package de.seemoo.at_tracking_detection.ui.scan
import android.bluetooth.le.ScanResult
-//import android.widget.TextView
-//import androidx.compose.ui.graphics.Color
-//import androidx.compose.ui.graphics.toArgb
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
@@ -20,10 +18,9 @@ import de.seemoo.at_tracking_detection.database.repository.ScanRepository
import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner
import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner.TIME_BETWEEN_BEACONS
import de.seemoo.at_tracking_detection.detection.LocationProvider
-//import de.seemoo.at_tracking_detection.util.Utility
import de.seemoo.at_tracking_detection.util.ble.BLEScanner
-import kotlinx.coroutines.MainScope
-import kotlinx.coroutines.async
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.LocalDateTime
import javax.inject.Inject
@@ -35,27 +32,31 @@ class ScanViewModel @Inject constructor(
private val locationProvider: LocationProvider,
) : ViewModel() {
val bluetoothDeviceListHighRisk = MutableLiveData>() // TODO: Problem needs to be immutable so that DiffUtil works
-
val bluetoothDeviceListLowRisk = MutableLiveData>()
val scanFinished = MutableLiveData(false)
- val sortingOrder = MutableLiveData(SortingOrder.SIGNAL_STRENGTH)
-
var bluetoothEnabled = MutableLiveData(true)
init {
- bluetoothDeviceListHighRisk.value = ArrayList()
- bluetoothDeviceListLowRisk.value = ArrayList()
+ bluetoothDeviceListHighRisk.value = mutableListOf()
+ bluetoothDeviceListLowRisk.value = mutableListOf()
bluetoothEnabled.value = BLEScanner.isBluetoothOn()
}
- private fun MutableLiveData.notifyObserver() {
- this.value = this.value
- }
-
- fun addScanResult(scanResult: ScanResult) {
+ fun addScanResult(scanResult: ScanResult) = viewModelScope.launch(Dispatchers.IO) {
if (scanFinished.value == true) {
- return
+ return@launch
+ }
+
+ val uniqueIdentifier = getPublicKey(scanResult) // either public key or MAC-Address
+
+ val bluetoothDeviceListHighRiskValue = bluetoothDeviceListHighRisk.value?.toMutableList() ?: mutableListOf()
+ val bluetoothDeviceListLowRiskValue = bluetoothDeviceListLowRisk.value?.toMutableList() ?: mutableListOf()
+
+ if (bluetoothDeviceListHighRiskValue.any { getPublicKey(it) == uniqueIdentifier } ||
+ bluetoothDeviceListLowRiskValue.any { getPublicKey(it) == uniqueIdentifier }) {
+ // If the scanResult is already in either of the lists, return early
+ return@launch
}
val deviceType = getDeviceType(scanResult)
@@ -63,11 +64,10 @@ class ScanViewModel @Inject constructor(
if (deviceType !in validDeviceTypes) {
// If device not selected in settings then do not add ScanResult to list or database
- return
+ return@launch
}
val currentDate = LocalDateTime.now()
- val uniqueIdentifier = getPublicKey(scanResult) // either public key or MAC-Address
if (beaconRepository.getNumberOfBeaconsAddress(
deviceAddress = uniqueIdentifier,
since = currentDate.minusMinutes(TIME_BETWEEN_BEACONS)
@@ -76,24 +76,19 @@ class ScanViewModel @Inject constructor(
val location = locationProvider.getLastLocation() // if not working: checkRequirements = false
Timber.d("Got location $location in ScanViewModel")
- MainScope().async {
- BackgroundBluetoothScanner.insertScanResult(
- scanResult = scanResult,
- latitude = location?.latitude,
- longitude = location?.longitude,
- altitude = location?.altitude,
- accuracy = location?.accuracy,
- discoveryDate = currentDate,
- )
- }
+ BackgroundBluetoothScanner.insertScanResult(
+ scanResult = scanResult,
+ latitude = location?.latitude,
+ longitude = location?.longitude,
+ altitude = location?.altitude,
+ accuracy = location?.accuracy,
+ discoveryDate = currentDate,
+ )
}
val deviceRepository = ATTrackingDetectionApplication.getCurrentApp().deviceRepository
val device = deviceRepository.getDevice(uniqueIdentifier)
- val bluetoothDeviceListHighRiskValue = bluetoothDeviceListHighRisk.value ?: return
- val bluetoothDeviceListLowRiskValue = bluetoothDeviceListLowRisk.value ?: return
-
bluetoothDeviceListHighRiskValue.removeIf {
getPublicKey(it) == uniqueIdentifier
}
@@ -104,18 +99,16 @@ class ScanViewModel @Inject constructor(
if (BaseDevice.getConnectionState(scanResult) in DeviceManager.unsafeConnectionState && ((device != null && !device.ignore) || device==null)) {
// only add possible devices to list
bluetoothDeviceListHighRiskValue.add(scanResult)
+
} else {
bluetoothDeviceListLowRiskValue.add(scanResult)
}
- sortResults(bluetoothDeviceListHighRiskValue)
- sortResults(bluetoothDeviceListLowRiskValue)
-
- val sortedHighRiskList = ArrayList(bluetoothDeviceListHighRiskValue)
- val sortedLowRiskList = ArrayList(bluetoothDeviceListLowRiskValue)
+ bluetoothDeviceListHighRiskValue.sortByDescending { it.rssi }
+ bluetoothDeviceListLowRiskValue.sortByDescending { it.rssi }
- bluetoothDeviceListHighRisk.postValue(sortedHighRiskList)
- bluetoothDeviceListLowRisk.postValue(sortedLowRiskList)
+ bluetoothDeviceListHighRisk.postValue(bluetoothDeviceListHighRiskValue)
+ bluetoothDeviceListLowRisk.postValue(bluetoothDeviceListLowRiskValue)
Timber.d("Adding scan result ${scanResult.device.address} with unique identifier $uniqueIdentifier")
Timber.d(
@@ -127,31 +120,6 @@ class ScanViewModel @Inject constructor(
Timber.d("Device list (Low Risk): ${bluetoothDeviceListLowRisk.value?.count()}")
}
- fun sortResults(bluetoothDeviceListValue: MutableList) {
- when(sortingOrder.value) {
- SortingOrder.SIGNAL_STRENGTH -> bluetoothDeviceListValue.sortByDescending { it.rssi }
- SortingOrder.DETECTION_ORDER -> bluetoothDeviceListValue.sortByDescending { it.timestampNanos }
- SortingOrder.ADDRESS -> bluetoothDeviceListValue.sortBy { it.device.address }
- else -> bluetoothDeviceListValue.sortByDescending { it.rssi }
- }
- }
-
-// fun changeColorOf(sortOptions: List, sortOption: TextView) {
-// val theme = Utility.getSelectedTheme()
-// var color = Color.Gray
-// if (theme){
-// color = Color.LightGray
-// }
-//
-// sortOptions.forEach {
-// if(it == sortOption) {
-// it.setBackgroundColor(color.toArgb())
-// } else {
-// it.setBackgroundColor(Color.Transparent.toArgb())
-// }
-// }
-// }
-
val isListEmpty: LiveData = MediatorLiveData().apply {
// Function to update the isListEmpty LiveData
fun update() {
diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml
index 750e0720..96ed13bd 100644
--- a/app/src/main/res/layout/fragment_scan.xml
+++ b/app/src/main/res/layout/fragment_scan.xml
@@ -25,87 +25,6 @@
android:layout_height="match_parent"
tools:context=".ui.scan.ScanFragment">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Date: Tue, 14 May 2024 15:34:48 +0200
Subject: [PATCH 132/154] replace ScanResult in manual scan with Wrapper
Object. Massive Speed Improvements
---
.../ui/scan/BluetoothDeviceAdapter.kt | 24 ++++-----
.../ui/scan/ScanFragment.kt | 5 --
.../ui/scan/ScanResultWrapper.kt | 24 +++++++++
.../ui/scan/ScanViewModel.kt | 50 +++++++++----------
.../util/BindingAdapter.kt | 24 +++++----
app/src/main/res/layout/item_scan_result.xml | 10 ++--
6 files changed, 78 insertions(+), 59 deletions(-)
create mode 100644 app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
index e5a71d1b..5d4c5171 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
@@ -1,6 +1,5 @@
package de.seemoo.at_tracking_detection.ui.scan
-import android.bluetooth.le.ScanResult
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.navigation.findNavController
@@ -9,18 +8,17 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.card.MaterialCardView
import de.seemoo.at_tracking_detection.R
-import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
import de.seemoo.at_tracking_detection.databinding.ItemScanResultBinding
import timber.log.Timber
class BluetoothDeviceAdapter:
- ListAdapter(BluetoothDeviceDiffCallback()) {
+ ListAdapter(BluetoothDeviceDiffCallback()) {
class ScanResultViewHolder(private val binding: ItemScanResultBinding) :
RecyclerView.ViewHolder(binding.root) {
- fun bind(scanResult: ScanResult) {
- binding.scanResult = scanResult
+ fun bind(wrappedScanResult: ScanResultWrapper) {
+ binding.wrappedScanResult = wrappedScanResult
binding.executePendingBindings()
}
}
@@ -33,13 +31,13 @@ class BluetoothDeviceAdapter:
}
override fun onBindViewHolder(holder: ScanResultViewHolder, position: Int) {
- val scanResult: ScanResult = getItem(position)
- holder.bind(scanResult)
+ val wrappedScanResult: ScanResultWrapper = getItem(position)
+ holder.bind(wrappedScanResult)
holder.itemView.findViewById(R.id.scan_result_item_card)
.setOnClickListener {
try {
- val deviceAddress: String = getPublicKey(scanResult)
+ val deviceAddress: String = wrappedScanResult.uniqueIdentifier
val directions = ScanFragmentDirections.actionScanToTrackingFragment(deviceAddress)
holder.itemView.findNavController()
.navigate(directions)
@@ -50,12 +48,12 @@ class BluetoothDeviceAdapter:
}
}
-class BluetoothDeviceDiffCallback: DiffUtil.ItemCallback() {
- override fun areItemsTheSame(oldItem: ScanResult, newItem: ScanResult): Boolean {
- return oldItem.device.address == newItem.device.address
+class BluetoothDeviceDiffCallback: DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: ScanResultWrapper, newItem: ScanResultWrapper): Boolean {
+ return oldItem.uniqueIdentifier == newItem.uniqueIdentifier
}
- override fun areContentsTheSame(oldItem: ScanResult, newItem: ScanResult): Boolean {
- return (oldItem.device.address == newItem.device.address) && (oldItem.rssi == newItem.rssi)
+ override fun areContentsTheSame(oldItem: ScanResultWrapper, newItem: ScanResultWrapper): Boolean {
+ return (oldItem.uniqueIdentifier == newItem.uniqueIdentifier) && (oldItem.rssi == newItem.rssi)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
index 5b52b7f8..f463bc3b 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
@@ -1,6 +1,5 @@
package de.seemoo.at_tracking_detection.ui.scan
-import android.annotation.SuppressLint
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.os.Bundle
@@ -30,7 +29,6 @@ class ScanFragment : Fragment() {
private val bluetoothDeviceAdapterHighRisk = BluetoothDeviceAdapter()
private val bluetoothDeviceAdapterLowRisk = BluetoothDeviceAdapter()
- @SuppressLint("NotifyDataSetChanged")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -46,12 +44,9 @@ class ScanFragment : Fragment() {
scanViewModel.bluetoothDeviceListHighRisk.observe(viewLifecycleOwner) {newList ->
bluetoothDeviceAdapterHighRisk.submitList(newList)
- bluetoothDeviceAdapterHighRisk.notifyDataSetChanged()
}
-
scanViewModel.bluetoothDeviceListLowRisk.observe(viewLifecycleOwner) {newList ->
bluetoothDeviceAdapterLowRisk.submitList(newList)
- bluetoothDeviceAdapterLowRisk.notifyDataSetChanged()
}
scanViewModel.scanFinished.observe(viewLifecycleOwner) {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt
new file mode 100644
index 00000000..97c29459
--- /dev/null
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt
@@ -0,0 +1,24 @@
+package de.seemoo.at_tracking_detection.ui.scan
+
+import android.bluetooth.le.ScanResult
+import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getConnectionState
+import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
+import de.seemoo.at_tracking_detection.database.models.device.DeviceManager.getDeviceType
+
+data class ScanResultWrapper(val scanResult: ScanResult){
+ val deviceAddress: String = scanResult.device.address
+ var rssi: Int = scanResult.rssi
+ var txPower: Int = scanResult.txPower
+ var isConnectable: Boolean = scanResult.isConnectable
+ val uniqueIdentifier = getPublicKey(scanResult) // either public key or MAC-Address
+ val deviceType = getDeviceType(scanResult)
+ var connectionState = getConnectionState(scanResult)
+
+ override fun hashCode(): Int {
+ return scanResult.hashCode()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return super.equals(other)
+ }
+}
\ No newline at end of file
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 bc1acac0..5f3fd6c0 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
@@ -8,10 +8,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
-import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
-import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
import de.seemoo.at_tracking_detection.database.models.device.DeviceManager
-import de.seemoo.at_tracking_detection.database.models.device.DeviceManager.getDeviceType
import de.seemoo.at_tracking_detection.database.models.device.DeviceType.Companion.getAllowedDeviceTypesFromSettings
import de.seemoo.at_tracking_detection.database.repository.BeaconRepository
import de.seemoo.at_tracking_detection.database.repository.ScanRepository
@@ -31,8 +28,8 @@ class ScanViewModel @Inject constructor(
private val beaconRepository: BeaconRepository,
private val locationProvider: LocationProvider,
) : ViewModel() {
- val bluetoothDeviceListHighRisk = MutableLiveData>() // TODO: Problem needs to be immutable so that DiffUtil works
- val bluetoothDeviceListLowRisk = MutableLiveData>()
+ val bluetoothDeviceListHighRisk = MutableLiveData>()
+ val bluetoothDeviceListLowRisk = MutableLiveData>()
val scanFinished = MutableLiveData(false)
@@ -48,28 +45,21 @@ class ScanViewModel @Inject constructor(
return@launch
}
- val uniqueIdentifier = getPublicKey(scanResult) // either public key or MAC-Address
+ val wrappedScanResult = ScanResultWrapper(scanResult)
val bluetoothDeviceListHighRiskValue = bluetoothDeviceListHighRisk.value?.toMutableList() ?: mutableListOf()
val bluetoothDeviceListLowRiskValue = bluetoothDeviceListLowRisk.value?.toMutableList() ?: mutableListOf()
- if (bluetoothDeviceListHighRiskValue.any { getPublicKey(it) == uniqueIdentifier } ||
- bluetoothDeviceListLowRiskValue.any { getPublicKey(it) == uniqueIdentifier }) {
- // If the scanResult is already in either of the lists, return early
- return@launch
- }
-
- val deviceType = getDeviceType(scanResult)
val validDeviceTypes = getAllowedDeviceTypesFromSettings()
- if (deviceType !in validDeviceTypes) {
+ if (wrappedScanResult.deviceType !in validDeviceTypes) {
// If device not selected in settings then do not add ScanResult to list or database
return@launch
}
val currentDate = LocalDateTime.now()
if (beaconRepository.getNumberOfBeaconsAddress(
- deviceAddress = uniqueIdentifier,
+ deviceAddress = wrappedScanResult.uniqueIdentifier,
since = currentDate.minusMinutes(TIME_BETWEEN_BEACONS)
) == 0) {
// There was no beacon with the address saved in the last IME_BETWEEN_BEACONS minutes
@@ -87,21 +77,31 @@ class ScanViewModel @Inject constructor(
}
val deviceRepository = ATTrackingDetectionApplication.getCurrentApp().deviceRepository
- val device = deviceRepository.getDevice(uniqueIdentifier)
+ val device = deviceRepository.getDevice(wrappedScanResult.uniqueIdentifier)
- bluetoothDeviceListHighRiskValue.removeIf {
- getPublicKey(it) == uniqueIdentifier
+ val elementHighRisk: ScanResultWrapper? = bluetoothDeviceListHighRiskValue.find {
+ it.uniqueIdentifier == wrappedScanResult.uniqueIdentifier
}
- bluetoothDeviceListLowRiskValue.removeIf {
- getPublicKey(it) == uniqueIdentifier
+ val elementLowRisk: ScanResultWrapper? = bluetoothDeviceListLowRiskValue.find {
+ it.uniqueIdentifier == wrappedScanResult.uniqueIdentifier
}
- if (BaseDevice.getConnectionState(scanResult) in DeviceManager.unsafeConnectionState && ((device != null && !device.ignore) || device==null)) {
+ if (elementHighRisk != null) {
+ elementHighRisk.rssi = wrappedScanResult.rssi
+ elementHighRisk.txPower = wrappedScanResult.txPower
+ elementHighRisk.isConnectable = wrappedScanResult.isConnectable
+ } else if (wrappedScanResult.connectionState in DeviceManager.unsafeConnectionState && ((device != null && !device.ignore) || device==null)) {
// only add possible devices to list
- bluetoothDeviceListHighRiskValue.add(scanResult)
+ bluetoothDeviceListHighRiskValue.add(wrappedScanResult)
+ }
- } else {
- bluetoothDeviceListLowRiskValue.add(scanResult)
+ if (elementLowRisk != null) {
+ elementLowRisk.rssi = wrappedScanResult.rssi
+ elementLowRisk.txPower = elementLowRisk.txPower
+ elementLowRisk.isConnectable = wrappedScanResult.isConnectable
+ } else if ((wrappedScanResult.connectionState !in DeviceManager.unsafeConnectionState || (device == null || device.ignore)) && elementHighRisk == null ) {
+ // only add possible devices to list
+ bluetoothDeviceListLowRiskValue.add(wrappedScanResult)
}
bluetoothDeviceListHighRiskValue.sortByDescending { it.rssi }
@@ -110,7 +110,7 @@ class ScanViewModel @Inject constructor(
bluetoothDeviceListHighRisk.postValue(bluetoothDeviceListHighRiskValue)
bluetoothDeviceListLowRisk.postValue(bluetoothDeviceListLowRiskValue)
- Timber.d("Adding scan result ${scanResult.device.address} with unique identifier $uniqueIdentifier")
+ Timber.d("Adding scan result ${scanResult.device.address} with unique identifier $wrappedScanResult.uniqueIdentifier")
Timber.d(
"status bytes: ${
scanResult.scanRecord?.manufacturerSpecificData?.get(76)?.get(2)?.toString(2)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
index d4825929..13d8b30e 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
@@ -1,6 +1,5 @@
package de.seemoo.at_tracking_detection.util
-import android.bluetooth.le.ScanResult
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
@@ -9,19 +8,20 @@ import androidx.recyclerview.widget.RecyclerView
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
-import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
+import de.seemoo.at_tracking_detection.database.models.device.DeviceType
+import de.seemoo.at_tracking_detection.ui.scan.ScanResultWrapper
@BindingAdapter("setAdapter")
fun RecyclerView.bindRecyclerViewAdapter(adapter: RecyclerView.Adapter<*>) {
this.run {
- this.setHasFixedSize(true)
+ this.setHasFixedSize(false)
this.adapter = adapter
}
}
@BindingAdapter("setSignalStrengthDrawable", requireAll = true)
-fun setSignalStrengthDrawable(imageView: ImageView, scanResult: ScanResult) {
- val rssi: Int = scanResult.rssi
+fun setSignalStrengthDrawable(imageView: ImageView, wrappedScanResult: ScanResultWrapper) {
+ val rssi: Int = wrappedScanResult.rssi
val quality = Utility.dbmToQuality(rssi)
when (quality) {
@@ -34,19 +34,21 @@ fun setSignalStrengthDrawable(imageView: ImageView, scanResult: ScanResult) {
@BindingAdapter("setDeviceDrawable", requireAll = true)
-fun setDeviceDrawable(imageView: ImageView, scanResult: ScanResult) {
- val device = BaseDevice(scanResult).device
- imageView.setImageDrawable(device.getDrawable())
+fun setDeviceDrawable(imageView: ImageView, wrappedScanResult: ScanResultWrapper) {
+ val drawableResId = DeviceType.getImageDrawable(wrappedScanResult.deviceType)
+ val drawable = ContextCompat.getDrawable(imageView.context, drawableResId)
+ imageView.setImageDrawable(drawable)
}
@BindingAdapter("setDeviceName", requireAll = true)
-fun setDeviceName (textView: TextView, scanResult: ScanResult) {
+fun setDeviceName (textView: TextView, wrappedScanResult: ScanResultWrapper) {
val deviceRepository = ATTrackingDetectionApplication.getCurrentApp().deviceRepository
- val deviceFromDb = deviceRepository.getDevice(getPublicKey(scanResult))
+ val deviceFromDb = deviceRepository.getDevice(wrappedScanResult.uniqueIdentifier)
if (deviceFromDb?.name != null) {
textView.text = deviceFromDb.getDeviceNameWithID()
} else {
- val device = BaseDevice(scanResult).device
+ // TODO: this can be optimized
+ val device = BaseDevice(wrappedScanResult.scanResult).device
textView.text = device.deviceContext.defaultDeviceName
}
}
diff --git a/app/src/main/res/layout/item_scan_result.xml b/app/src/main/res/layout/item_scan_result.xml
index 1916a9c0..23218069 100644
--- a/app/src/main/res/layout/item_scan_result.xml
+++ b/app/src/main/res/layout/item_scan_result.xml
@@ -6,8 +6,8 @@
+ name="wrappedScanResult"
+ type="de.seemoo.at_tracking_detection.ui.scan.ScanResultWrapper" />
@@ -50,7 +50,7 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toLeftOf="@id/scan_result_guideline_left"
- app:setDeviceDrawable="@{scanResult}"
+ app:setDeviceDrawable="@{wrappedScanResult}"
app:tint="?android:attr/textColorTertiary"
tools:src="@drawable/ic_airtag" />
@@ -68,7 +68,7 @@
app:layout_constraintRight_toLeftOf="@id/scan_result_guideline_right"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:setDeviceName="@{scanResult}"
+ app:setDeviceName="@{wrappedScanResult}"
tools:text="Debug Device" />
+ app:setSignalStrengthDrawable="@{wrappedScanResult}" />
Date: Wed, 15 May 2024 15:44:35 +0200
Subject: [PATCH 133/154] Setting minimum detection events to 3 on medium
security mode. Due to internal testing we found that this would lead to more
reliable results
---
.../database/models/device/types/Chipolo.kt | 11 ------
.../database/models/device/types/Tile.kt | 7 ++--
.../util/risk/RiskLevelEvaluator.kt | 6 ++--
.../at_tracking_detection/ExampleUnitTest.kt | 34 +++++++++----------
4 files changed, 22 insertions(+), 36 deletions(-)
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 c7f22472..0e8dbe3c 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
@@ -40,17 +40,6 @@ class Chipolo(val id: Int) : Device() {
override val websiteManufacturer: String
get() = "https://chipolo.net/"
- override val numberOfHoursToBeConsideredForTrackingDetection: Long
- get() = super.numberOfHoursToBeConsideredForTrackingDetection
-
- override val numberOfLocationsToBeConsideredForTrackingDetectionLow: Int
- get() = super.numberOfLocationsToBeConsideredForTrackingDetectionLow
-
- override val numberOfLocationsToBeConsideredForTrackingDetectionMedium: Int
- get() = super.numberOfLocationsToBeConsideredForTrackingDetectionMedium
-
- override val numberOfLocationsToBeConsideredForTrackingDetectionHigh: Int
- get() = super.numberOfLocationsToBeConsideredForTrackingDetectionHigh
val offlineFindingServiceUUID: ParcelUuid = ParcelUuid.fromString("0000FE33-0000-1000-8000-00805F9B34FB")
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 fafd76e7..187413d9 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
@@ -41,14 +41,11 @@ class Tile(val id: Int) : Device(){
override val statusByteDeviceType: UInt
get() = 0u
- override val numberOfHoursToBeConsideredForTrackingDetection: Long
- get() = super.numberOfHoursToBeConsideredForTrackingDetection
-
override val numberOfLocationsToBeConsideredForTrackingDetectionLow: Int
- get() = 8
+ get() = 5
override val numberOfLocationsToBeConsideredForTrackingDetectionMedium: Int
- get() = 5
+ get() = 4
override val numberOfLocationsToBeConsideredForTrackingDetectionHigh: Int
get() = 3
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
index 042db9cb..abac5ab7 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/risk/RiskLevelEvaluator.kt
@@ -107,9 +107,9 @@ class RiskLevelEvaluator(
private const val MINUTES_AT_LEAST_TRACKED_BEFORE_ALARM_LOW: Long = 120
// Default Values:
- const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_HIGH: Int = 3
- const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM: Int = 4
- const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW: Int = 5
+ const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_HIGH: Int = 2
+ const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_MEDIUM: Int = 3
+ const val NUMBER_OF_LOCATIONS_BEFORE_ALARM_LOW: Int = 4
private fun getAtLeastTrackedSince(): LocalDateTime = LocalDateTime.now().minusMinutes(
getMinutesAtLeastTrackedBeforeAlarm()
diff --git a/app/src/test/java/de/seemoo/at_tracking_detection/ExampleUnitTest.kt b/app/src/test/java/de/seemoo/at_tracking_detection/ExampleUnitTest.kt
index f522a0a2..4b1c8b3a 100644
--- a/app/src/test/java/de/seemoo/at_tracking_detection/ExampleUnitTest.kt
+++ b/app/src/test/java/de/seemoo/at_tracking_detection/ExampleUnitTest.kt
@@ -1,20 +1,20 @@
package de.seemoo.at_tracking_detection
import de.seemoo.at_tracking_detection.detection.TrackingDetectorWorker
-import org.junit.Test
-
-import org.junit.Assert.*
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
-
-
-}
\ No newline at end of file
+//import org.junit.Test
+//
+//import org.junit.Assert.*
+//
+///**
+// * Example local unit test, which will execute on the development machine (host).
+// *
+// * See [testing documentation](http://d.android.com/tools/testing).
+// */
+//class ExampleUnitTest {
+// @Test
+// fun addition_isCorrect() {
+// assertEquals(4, 2 + 2)
+// }
+//
+//
+//}
\ No newline at end of file
From 7da26e012e0bf317e8bc29e1a7263d4c52a4b5f6 Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Wed, 15 May 2024 18:08:47 +0200
Subject: [PATCH 134/154] Updating gradle and manifest
---
app/build.gradle | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index a81c562e..97339002 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -26,8 +26,8 @@ android {
applicationId "de.seemoo.at_tracking_detection"
minSdkVersion 28
targetSdk = 34
- versionCode 40
- versionName "2.1.1"
+ versionCode 41
+ versionName "2.2"
buildConfigField "String", "API_KEY", apiProperties["API_KEY"]
buildConfigField "String", "API_BASE_ADDRESS", apiProperties["API_BASE_ADDRESS"]
From 88f5e3bc550aab8eb676f93268205c9fb3b21e8e Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Wed, 15 May 2024 18:08:55 +0200
Subject: [PATCH 135/154] Updating gradle and manifest
---
app/src/main/AndroidManifest.xml | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f3b45eb8..b6964ea1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,38 +1,48 @@
-
+
+
+
+
+
+
+
-
+
+
+
+
+
-
+
From 536da88f1ffcf3c76dbd23878b1cd718d9d8559c Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Thu, 16 May 2024 08:51:32 +0200
Subject: [PATCH 136/154] Testing app without FOREGROUND_SERVICE - Optimizing
setAlarm for Android 14
---
app/src/main/AndroidManifest.xml | 4 ---
.../worker/BackgroundWorkScheduler.kt | 28 ++++++++-----------
2 files changed, 12 insertions(+), 20 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b6964ea1..7d2be2ca 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -38,10 +38,6 @@
-
-
-
-
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
index 21e1db10..0532eee1 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
@@ -148,25 +148,21 @@ class BackgroundWorkScheduler @Inject constructor(
Context.ALARM_SERVICE) as AlarmManager
// We use exact Alarms since we want regular background scans to happen.
-
- if (Utility.checkPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)) {
+ try {
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ Timber.d("Scheduled an setExactAndAllowWhileIdle alarm to start a scan at $alarmDate")
+ }catch (exception: SecurityException) {
try {
- alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
- Timber.d("Scheduled an setExactAndAllowWhileIdle alarm to start a scan at $alarmDate")
+ Timber.w("Failed scheduling setExactAndAllowWhileIdle Alarm scan")
+ // Alarm could not be scheduled because user disallowed battery exception
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ Timber.d("Scheduled an setExact alarm to start a scan at $alarmDate")
}catch (exception: SecurityException) {
- try {
- Timber.w("Failed scheduling setExactAndAllowWhileIdle Alarm scan")
- // Alarm could not be scheduled because user disallowed battery exception
- alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
- Timber.d("Scheduled an setExact alarm to start a scan at $alarmDate")
- }catch (exception: SecurityException) {
- Timber.w("Failed scheduling setExact Alarm scan")
- }
+ Timber.w("Failed scheduling setExact Alarm scan")
+ // Alarm could not be scheduled because user disallowed battery exception
+ alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ Timber.d("Scheduled an set alarm to start a scan at $alarmDate")
}
- }else {
- // Alarm could not be scheduled because user disallowed battery exception
- alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
- Timber.d("Scheduled an set alarm to start a scan at $alarmDate")
}
SharedPrefs.nextScanDate = alarmDate
From aa3f5ed80d0dbc6a8fca7a102dfec5ab3ef5e9ff Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 16 May 2024 11:06:48 +0200
Subject: [PATCH 137/154] Rssi indicator image in manual scan updates if rssi
value changes, update some dependencies
---
app/build.gradle | 25 +++++++++---------
.../ui/scan/BluetoothDeviceAdapter.kt | 2 +-
.../ui/scan/ScanFragment.kt | 14 ++++++++++
.../ui/scan/ScanResultWrapper.kt | 4 ++-
.../ui/scan/ScanViewModel.kt | 12 +++++----
.../util/BindingAdapter.kt | 26 +++++++++++++------
6 files changed, 56 insertions(+), 27 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 97339002..0e023d98 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -94,15 +94,16 @@ dependencies {
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation 'com.github.bastienpaulfr:Treessence:1.0.0'
implementation "androidx.work:work-runtime-ktx:$work_version"
- implementation 'androidx.core:core-ktx:1.12.0'
+ implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'com.google.android.material:material:1.11.0'
+ implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
+ 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.7.0'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0'
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
@@ -113,7 +114,7 @@ dependencies {
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.12.0'
+ implementation 'androidx.core:core-ktx:1.13.1'
debugImplementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12'
implementation "com.google.dagger:hilt-android:$hilt_version"
@@ -158,17 +159,17 @@ dependencies {
//Compose
// Integration with activities
- implementation 'androidx.activity:activity-compose:1.8.2'
+ implementation 'androidx.activity:activity-compose:1.9.0'
// Compose Material Design
- implementation 'androidx.compose.material:material:1.6.5'
+ implementation 'androidx.compose.material:material:1.6.7'
// Animations
- implementation 'androidx.compose.animation:animation:1.6.5'
+ implementation 'androidx.compose.animation:animation:1.6.7'
// Tooling support (Previews, etc.)
- implementation 'androidx.compose.ui:ui-tooling:1.6.5'
+ implementation 'androidx.compose.ui:ui-tooling:1.6.7'
// Integration with ViewModels
- implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0'
// UI Tests
- androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.5'
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.7'
// When using a MDC theme
implementation "com.google.android.material:compose-theme-adapter:1.2.1"
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
index 5d4c5171..f202c147 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
@@ -54,6 +54,6 @@ class BluetoothDeviceDiffCallback: DiffUtil.ItemCallback() {
}
override fun areContentsTheSame(oldItem: ScanResultWrapper, newItem: ScanResultWrapper): Boolean {
- return (oldItem.uniqueIdentifier == newItem.uniqueIdentifier) && (oldItem.rssi == newItem.rssi)
+ return (oldItem.uniqueIdentifier == newItem.uniqueIdentifier) && (oldItem.rssiValue == newItem.rssiValue)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
index f463bc3b..56fb7d65 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
@@ -158,6 +158,20 @@ class ScanFragment : Fragment() {
stopBluetoothScan()
}
+ override fun onResume() {
+ super.onResume()
+ if (scanViewModel.scanFinished.value == false) {
+ startBluetoothScan()
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ if (scanViewModel.scanFinished.value == false) {
+ stopBluetoothScan()
+ }
+ }
+
companion object {
private const val SCAN_DURATION = 60_000L
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt
index 97c29459..c0c9f8e7 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt
@@ -1,13 +1,15 @@
package de.seemoo.at_tracking_detection.ui.scan
import android.bluetooth.le.ScanResult
+import androidx.databinding.ObservableField
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getConnectionState
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
import de.seemoo.at_tracking_detection.database.models.device.DeviceManager.getDeviceType
data class ScanResultWrapper(val scanResult: ScanResult){
val deviceAddress: String = scanResult.device.address
- var rssi: Int = scanResult.rssi
+ val rssi: ObservableField = ObservableField(scanResult.rssi) // This is so the image can update itself live
+ var rssiValue: Int = scanResult.rssi
var txPower: Int = scanResult.txPower
var isConnectable: Boolean = scanResult.isConnectable
val uniqueIdentifier = getPublicKey(scanResult) // either public key or MAC-Address
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 5f3fd6c0..ed75d373 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
@@ -87,7 +87,8 @@ class ScanViewModel @Inject constructor(
}
if (elementHighRisk != null) {
- elementHighRisk.rssi = wrappedScanResult.rssi
+ elementHighRisk.rssi.set(wrappedScanResult.rssi.get())
+ elementHighRisk.rssiValue = wrappedScanResult.rssiValue
elementHighRisk.txPower = wrappedScanResult.txPower
elementHighRisk.isConnectable = wrappedScanResult.isConnectable
} else if (wrappedScanResult.connectionState in DeviceManager.unsafeConnectionState && ((device != null && !device.ignore) || device==null)) {
@@ -96,16 +97,17 @@ class ScanViewModel @Inject constructor(
}
if (elementLowRisk != null) {
- elementLowRisk.rssi = wrappedScanResult.rssi
- elementLowRisk.txPower = elementLowRisk.txPower
+ elementLowRisk.rssi.set(wrappedScanResult.rssi.get())
+ elementLowRisk.rssiValue = wrappedScanResult.rssiValue
+ elementLowRisk.txPower = wrappedScanResult.txPower
elementLowRisk.isConnectable = wrappedScanResult.isConnectable
} else if ((wrappedScanResult.connectionState !in DeviceManager.unsafeConnectionState || (device == null || device.ignore)) && elementHighRisk == null ) {
// only add possible devices to list
bluetoothDeviceListLowRiskValue.add(wrappedScanResult)
}
- bluetoothDeviceListHighRiskValue.sortByDescending { it.rssi }
- bluetoothDeviceListLowRiskValue.sortByDescending { it.rssi }
+ 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/util/BindingAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
index 13d8b30e..620f5182 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/util/BindingAdapter.kt
@@ -4,6 +4,7 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.databinding.BindingAdapter
+import androidx.databinding.Observable
import androidx.recyclerview.widget.RecyclerView
import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
import de.seemoo.at_tracking_detection.R
@@ -21,15 +22,24 @@ fun RecyclerView.bindRecyclerViewAdapter(adapter: RecyclerView.Adapter<*>) {
@BindingAdapter("setSignalStrengthDrawable", requireAll = true)
fun setSignalStrengthDrawable(imageView: ImageView, wrappedScanResult: ScanResultWrapper) {
- val rssi: Int = wrappedScanResult.rssi
- val quality = Utility.dbmToQuality(rssi)
-
- when (quality) {
- 0 -> imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, R.drawable.ic_signal_low))
- 1 -> imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, R.drawable.ic_signal_middle_low))
- 2 -> imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, R.drawable.ic_signal_middle_high))
- 3 -> imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, R.drawable.ic_signal_high))
+ fun setImage(rssiValue: Int) {
+ val quality = Utility.dbmToQuality(rssiValue)
+
+ when (quality) {
+ 0 -> imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, R.drawable.ic_signal_low))
+ 1 -> imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, R.drawable.ic_signal_middle_low))
+ 2 -> imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, R.drawable.ic_signal_middle_high))
+ 3 -> imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, R.drawable.ic_signal_high))
+ }
}
+
+ setImage(wrappedScanResult.rssiValue)
+
+ wrappedScanResult.rssi.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
+ override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
+ setImage(wrappedScanResult.rssi.get() ?: wrappedScanResult.rssiValue)
+ }
+ })
}
From f15ee710c603a5e6e4cd83c18c2de036c029ca63 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 16 May 2024 11:14:20 +0200
Subject: [PATCH 138/154] optimize Repository Loading in manual scan
---
.../ui/dashboard/DashboardViewModel.kt | 4 ++--
.../seemoo/at_tracking_detection/ui/scan/ScanViewModel.kt | 6 ++----
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardViewModel.kt
index e19c44be..3d35eac0 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/dashboard/DashboardViewModel.kt
@@ -20,10 +20,10 @@ import javax.inject.Inject
@HiltViewModel
class DashboardViewModel @Inject constructor(
val beaconRepository: BeaconRepository,
- notificationRepository: NotificationRepository,
+ val notificationRepository: NotificationRepository,
val deviceRepository: DeviceRepository,
private val sharedPreferences: SharedPreferences,
- backgroundWorkScheduler: BackgroundWorkScheduler
+ val backgroundWorkScheduler: BackgroundWorkScheduler
) : ViewModel() {
private var lastScan: LocalDateTime
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 ed75d373..70f40cb8 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
@@ -7,11 +7,10 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
-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.DeviceType.Companion.getAllowedDeviceTypesFromSettings
import de.seemoo.at_tracking_detection.database.repository.BeaconRepository
-import de.seemoo.at_tracking_detection.database.repository.ScanRepository
+import de.seemoo.at_tracking_detection.database.repository.DeviceRepository
import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner
import de.seemoo.at_tracking_detection.detection.BackgroundBluetoothScanner.TIME_BETWEEN_BEACONS
import de.seemoo.at_tracking_detection.detection.LocationProvider
@@ -24,7 +23,7 @@ import javax.inject.Inject
@HiltViewModel
class ScanViewModel @Inject constructor(
- private val scanRepository: ScanRepository,
+ private val deviceRepository: DeviceRepository,
private val beaconRepository: BeaconRepository,
private val locationProvider: LocationProvider,
) : ViewModel() {
@@ -76,7 +75,6 @@ class ScanViewModel @Inject constructor(
)
}
- val deviceRepository = ATTrackingDetectionApplication.getCurrentApp().deviceRepository
val device = deviceRepository.getDevice(wrappedScanResult.uniqueIdentifier)
val elementHighRisk: ScanResultWrapper? = bluetoothDeviceListHighRiskValue.find {
From 063274c507c00eaf6c77b4a719a10a248896733d Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 19 May 2024 14:36:13 +0200
Subject: [PATCH 139/154] further use of ScanResultWrapper in Backend. Put back
Exact Alarm Permissions
---
.../detection/BackgroundBluetoothScanner.kt | 79 ++++++++++---------
.../ui/scan/ScanResultWrapper.kt | 2 +
.../ui/scan/ScanViewModel.kt | 2 +-
.../worker/BackgroundWorkScheduler.kt | 34 +++++---
4 files changed, 66 insertions(+), 51 deletions(-)
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 1f76746f..527095a0 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
@@ -20,6 +20,7 @@ import de.seemoo.at_tracking_detection.database.models.device.DeviceManager
import de.seemoo.at_tracking_detection.database.models.device.DeviceType
import de.seemoo.at_tracking_detection.database.repository.ScanRepository
import de.seemoo.at_tracking_detection.notifications.NotificationService
+import de.seemoo.at_tracking_detection.ui.scan.ScanResultWrapper
import de.seemoo.at_tracking_detection.util.SharedPrefs
import de.seemoo.at_tracking_detection.util.Utility
import de.seemoo.at_tracking_detection.util.ble.BLEScanCallback
@@ -59,17 +60,17 @@ object BackgroundBluetoothScanner {
get() {
return ATTrackingDetectionApplication.getCurrentApp().notificationService
}
- val locationProvider: LocationProvider
+ private val locationProvider: LocationProvider
get() {
return ATTrackingDetectionApplication.getCurrentApp().locationProvider
}
- val scanRepository: ScanRepository
+ private val scanRepository: ScanRepository
get() {
return ATTrackingDetectionApplication.getCurrentApp().scanRepository
}
- var isScanning = false
+ private var isScanning = false
class BackgroundScanResults(var duration: Long, var scanMode: Int, var numberDevicesFound: Int, var failed: Boolean)
suspend fun scanInBackground(startedFrom: String): BackgroundScanResults {
if (isScanning) {
@@ -79,9 +80,7 @@ object BackgroundBluetoothScanner {
Timber.d("Starting BackgroundBluetoothScanner from $startedFrom")
val scanMode = getScanMode()
- val scanId =
- scanRepository.insert(Scan(startDate = LocalDateTime.now(), isManual = false, scanMode = scanMode))
-
+ val scanId = scanRepository.insert(Scan(startDate = LocalDateTime.now(), isManual = false, scanMode = scanMode))
if (!Utility.checkBluetoothPermission()) {
Timber.d("Permission to perform bluetooth scan missing")
@@ -91,6 +90,10 @@ object BackgroundBluetoothScanner {
val bluetoothManager =
applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter
+ if (bluetoothAdapter.bluetoothLeScanner == null) {
+ Timber.e("BluetoothLeScanner not found!")
+ return BackgroundScanResults(0, 0, 0, true)
+ }
} catch (e: Throwable) {
Timber.e("BluetoothAdapter not found!")
return BackgroundScanResults(0, 0, 0, true)
@@ -113,6 +116,9 @@ object BackgroundBluetoothScanner {
// Returns the last known location if this matches our requirements or starts new location updates
locationFetchStarted = System.currentTimeMillis()
location = locationProvider.lastKnownOrRequestLocationUpdates(locationRequester = locationRequester, timeoutMillis = LOCATION_UPDATE_MAX_TIME_MS - 2000L)
+ if (location == null) {
+ Timber.e("Failed to retrieve location")
+ }
}
//Starting BLE Scan
@@ -143,11 +149,11 @@ object BackgroundBluetoothScanner {
//Adding all scan results to the database after the scan has finished
scanResultDictionary.forEach { (_, discoveredDevice) ->
- val deviceType = DeviceManager.getDeviceType(discoveredDevice.scanResult)
+ val deviceType = discoveredDevice.wrappedScanResult.deviceType
if (deviceType in validDeviceTypes) {
insertScanResult(
- scanResult = discoveredDevice.scanResult,
+ wrappedScanResult = discoveredDevice.wrappedScanResult,
latitude = location?.latitude,
longitude = location?.longitude,
altitude = location?.altitude,
@@ -159,14 +165,12 @@ object BackgroundBluetoothScanner {
SharedPrefs.lastScanDate = LocalDateTime.now()
SharedPrefs.isScanningInBackground = false
- if (scanId != null) {
- val scan = scanRepository.scanWithId(scanId.toInt())
- if (scan != null) {
- scan.endDate = LocalDateTime.now()
- scan.duration = scanDuration.toInt() / 1000
- scan.noDevicesFound = scanResultDictionary.size
- scanRepository.update(scan)
- }
+ val scan = scanRepository.scanWithId(scanId.toInt())
+ if (scan != null) {
+ scan.endDate = LocalDateTime.now()
+ scan.duration = scanDuration.toInt() / 1000
+ scan.noDevicesFound = scanResultDictionary.size
+ scanRepository.update(scan)
}
Timber.d("Scheduling tracking detector worker")
@@ -188,11 +192,12 @@ object BackgroundBluetoothScanner {
private val leScanCallback: ScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, scanResult: ScanResult) {
super.onScanResult(callbackType, scanResult)
+ val wrappedScanResult = ScanResultWrapper(scanResult)
//Checks if the device has been found already
- if (!scanResultDictionary.containsKey(BaseDevice.getPublicKey(scanResult))) {
- Timber.d("Found ${scanResult.device.address} at ${LocalDateTime.now()}")
- scanResultDictionary[BaseDevice.getPublicKey(scanResult)] =
- DiscoveredDevice(scanResult, LocalDateTime.now())
+ if (!scanResultDictionary.containsKey(wrappedScanResult.uniqueIdentifier)) {
+ Timber.d("Found ${wrappedScanResult.uniqueIdentifier} at ${LocalDateTime.now()}")
+ scanResultDictionary[wrappedScanResult.uniqueIdentifier] =
+ DiscoveredDevice(wrappedScanResult, LocalDateTime.now())
}
}
@@ -265,16 +270,16 @@ object BackgroundBluetoothScanner {
}
}
- class DiscoveredDevice(var scanResult: ScanResult, var discoveryDate: LocalDateTime)
+ class DiscoveredDevice(var wrappedScanResult: ScanResultWrapper, var discoveryDate: LocalDateTime)
const val MAX_DISTANCE_UNTIL_NEW_LOCATION: Float = 150f // in meters
const val TIME_BETWEEN_BEACONS: Long =
15 // 15 minutes until the same beacon gets saved again in the db
- const val LOCATION_UPDATE_MAX_TIME_MS: Long =
+ private const val LOCATION_UPDATE_MAX_TIME_MS: Long =
122_000L // Wait maximum 122s to get a location update
suspend fun insertScanResult(
- scanResult: ScanResult,
+ wrappedScanResult: ScanResultWrapper,
latitude: Double?,
longitude: Double?,
altitude: Double?,
@@ -287,7 +292,7 @@ object BackgroundBluetoothScanner {
return Pair(null, null)
}
- val deviceSaved = saveDevice(scanResult, discoveryDate) ?: return Pair(
+ val deviceSaved = saveDevice(wrappedScanResult, discoveryDate) ?: return Pair(
null,
null
) // return when device does not qualify to be saved
@@ -302,22 +307,22 @@ object BackgroundBluetoothScanner {
)?.locationId
val beaconSaved =
- saveBeacon(scanResult, discoveryDate, locId) ?: return Pair(null, null)
+ saveBeacon(wrappedScanResult, discoveryDate, locId) ?: return Pair(null, null)
return Pair(deviceSaved, beaconSaved)
}
private suspend fun saveBeacon(
- scanResult: ScanResult,
+ wrappedScanResult: ScanResultWrapper,
discoveryDate: LocalDateTime,
locId: Int?
): Beacon? {
val beaconRepository =
ATTrackingDetectionApplication.getCurrentApp().beaconRepository
- val uuids = scanResult.scanRecord?.serviceUuids?.map { it.toString() }?.toList()
- val uniqueIdentifier = BaseDevice.getPublicKey(scanResult)
+ val uuids = wrappedScanResult.serviceUuids
+ val uniqueIdentifier = wrappedScanResult.uniqueIdentifier
- val connectionState: ConnectionState = BaseDevice.getConnectionState(scanResult)
+ val connectionState: ConnectionState = wrappedScanResult.connectionState
val connectionStateString = Utility.connectionStateToString(connectionState)
var beacon: Beacon? = null
@@ -331,12 +336,12 @@ object BackgroundBluetoothScanner {
beacon = if (BuildConfig.DEBUG) {
// Save the manufacturer data to the beacon
Beacon(
- discoveryDate, scanResult.rssi, BaseDevice.getPublicKey(scanResult), locId,
- scanResult.scanRecord?.bytes, uuids, connectionStateString
+ discoveryDate, wrappedScanResult.rssiValue, wrappedScanResult.uniqueIdentifier, locId,
+ wrappedScanResult.mfg, uuids, connectionStateString
)
} else {
Beacon(
- discoveryDate, scanResult.rssi, BaseDevice.getPublicKey(scanResult), locId,
+ discoveryDate, wrappedScanResult.rssiValue, wrappedScanResult.uniqueIdentifier, locId,
null, uuids, connectionStateString
)
}
@@ -358,28 +363,28 @@ object BackgroundBluetoothScanner {
}
private suspend fun saveDevice(
- scanResult: ScanResult,
+ wrappedScanResult: ScanResultWrapper,
discoveryDate: LocalDateTime
): BaseDevice? {
val deviceRepository =
ATTrackingDetectionApplication.getCurrentApp().deviceRepository
- val deviceAddress = BaseDevice.getPublicKey(scanResult)
+ val deviceAddress = wrappedScanResult.uniqueIdentifier
// Checks if Device already exists in device database
var device = deviceRepository.getDevice(deviceAddress)
if (device == null) {
// Do not Save Samsung Devices
- device = BaseDevice(scanResult)
+ device = BaseDevice(wrappedScanResult.scanResult)
// Check if ConnectionState qualifies Device to be saved
// Only Save when Device is offline long enough
- if (BaseDevice.getConnectionState(scanResult) !in DeviceManager.savedConnectionStates) {
+ if (wrappedScanResult.connectionState !in DeviceManager.savedConnectionStates) {
Timber.d("Device not in a saved connection state... Skipping!")
return null
}
- if (BaseDevice.getConnectionState(scanResult) !in DeviceManager.unsafeConnectionState) {
+ if (wrappedScanResult.connectionState !in DeviceManager.unsafeConnectionState) {
Timber.d("Device is safe and will be hidden to the user!")
device.safeTracker = true
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt
index c0c9f8e7..4bc01d67 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt
@@ -15,6 +15,8 @@ data class ScanResultWrapper(val scanResult: ScanResult){
val uniqueIdentifier = getPublicKey(scanResult) // either public key or MAC-Address
val deviceType = getDeviceType(scanResult)
var connectionState = getConnectionState(scanResult)
+ val serviceUuids = scanResult.scanRecord?.serviceUuids?.map { it.toString() }?.toList()
+ val mfg = scanResult.scanRecord?.bytes
override fun hashCode(): Int {
return scanResult.hashCode()
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 70f40cb8..b0682722 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
@@ -66,7 +66,7 @@ class ScanViewModel @Inject constructor(
Timber.d("Got location $location in ScanViewModel")
BackgroundBluetoothScanner.insertScanResult(
- scanResult = scanResult,
+ wrappedScanResult = wrappedScanResult,
latitude = location?.latitude,
longitude = location?.longitude,
altitude = location?.altitude,
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
index 0532eee1..fd6358c5 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
@@ -98,7 +98,7 @@ class BackgroundWorkScheduler @Inject constructor(
/**
* If the app does not scan in background for two hours the AlarmManager will execute and call the `ScheduleWorkersReceiver` to initiate a new background scan
*/
- @SuppressLint("UnspecifiedImmutableFlag")
+ // @SuppressLint("UnspecifiedImmutableFlag")
fun scheduleAlarmWakeupIfScansFail() {
//Run in 2 hours
// val timeInMillisUntilNotification: Long = 2 * 60 * 60 * 1000
@@ -148,21 +148,29 @@ class BackgroundWorkScheduler @Inject constructor(
Context.ALARM_SERVICE) as AlarmManager
// We use exact Alarms since we want regular background scans to happen.
- try {
- alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
- Timber.d("Scheduled an setExactAndAllowWhileIdle alarm to start a scan at $alarmDate")
- }catch (exception: SecurityException) {
+ if (Utility.checkPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)) {
try {
- Timber.w("Failed scheduling setExactAndAllowWhileIdle Alarm scan")
- // Alarm could not be scheduled because user disallowed battery exception
- alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
- Timber.d("Scheduled an setExact alarm to start a scan at $alarmDate")
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ Timber.d("Scheduled an setExactAndAllowWhileIdle alarm to start a scan at $alarmDate")
}catch (exception: SecurityException) {
- Timber.w("Failed scheduling setExact Alarm scan")
- // Alarm could not be scheduled because user disallowed battery exception
- alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
- Timber.d("Scheduled an set alarm to start a scan at $alarmDate")
+ try {
+ Timber.w("Failed scheduling setExactAndAllowWhileIdle Alarm scan")
+ // Alarm could not be scheduled because user disallowed battery exception
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ Timber.d("Scheduled an setExact alarm to start a scan at $alarmDate")
+ }catch (exception: SecurityException) {
+ Timber.w("Failed scheduling setExact Alarm scan")
+ // Alarm could not be scheduled because user disallowed battery exception
+ alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ Timber.d("Scheduled an set alarm to start a scan at $alarmDate")
+ }
+ } catch (exception: Exception) {
+ Timber.e("Unexpected exception while scheduling alarm: ${exception.message}")
}
+ } else {
+ // Alarm could not be scheduled because user disallowed battery exception
+ alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ Timber.d("Scheduled an set alarm to start a scan at $alarmDate")
}
SharedPrefs.nextScanDate = alarmDate
From c0656bd343cf99933e26a3c916e73aee9d5b0b13 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 19 May 2024 14:51:12 +0200
Subject: [PATCH 140/154] fix TypeToken Error on some devices by updating
proguard rules
---
app/proguard-rules.pro | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index c1071cd2..f76621e2 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -72,4 +72,17 @@
# R8 full mode strips generic signatures from return types if not kept.
-keep,allowobfuscation,allowshrinking class retrofit2.Response
--keep class de.seemoo.at_tracking_detection.** { *; }
\ No newline at end of file
+-keep class de.seemoo.at_tracking_detection.** { *; }
+
+# Keep Gson classes
+-keep class com.google.gson.reflect.TypeToken { *; }
+-keep class com.google.gson.Gson { *; }
+-keep class com.google.gson.TypeAdapter { *; }
+-keep class com.google.gson.stream.JsonReader { *; }
+-keep class com.google.gson.stream.JsonWriter { *; }
+
+# Ensure that the classes related to Article are not stripped
+-keep class de.seemoo.at_tracking_detection.ui.dashboard.Article { *; }
+
+# Keep ProGuard/R8 from stripping out important methods or classes
+-keep class * implements com.google.gson.reflect.TypeToken { *; }
\ No newline at end of file
From 2ce1de652eb01bb968c2a06afa3cb1666644b633 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 19 May 2024 15:44:16 +0200
Subject: [PATCH 141/154] add message in manual scan that Location Permission
has to turned on
---
.../detection/LocationProvider.kt | 10 ++++
.../ui/scan/ScanFragment.kt | 1 +
.../ui/scan/ScanViewModel.kt | 5 +-
app/src/main/res/layout/fragment_scan.xml | 14 ++++-
.../res/layout/include_location_disabled.xml | 58 +++++++++++++++++++
app/src/main/res/values-de/strings.xml | 2 +
app/src/main/res/values/strings.xml | 2 +
7 files changed, 90 insertions(+), 2 deletions(-)
create mode 100644 app/src/main/res/layout/include_location_disabled.xml
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 d414a383..f87df307 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
@@ -1,12 +1,15 @@
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.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
@@ -300,6 +303,13 @@ open class LocationProvider @Inject constructor(
const val MIN_DISTANCE_METER = 0.0F
const val MAX_AGE_SECONDS = 120L
const val MIN_ACCURACY_METER = 120L
+
+ 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)
+ }
}
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
index 56fb7d65..f975f44f 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanFragment.kt
@@ -19,6 +19,7 @@ import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import de.seemoo.at_tracking_detection.R
import de.seemoo.at_tracking_detection.databinding.FragmentScanBinding
+import de.seemoo.at_tracking_detection.detection.LocationProvider
import de.seemoo.at_tracking_detection.util.ble.BLEScanner
import timber.log.Timber
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 b0682722..5890f904 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
@@ -32,11 +32,14 @@ class ScanViewModel @Inject constructor(
val scanFinished = MutableLiveData(false)
- var bluetoothEnabled = MutableLiveData(true)
+ val bluetoothEnabled = MutableLiveData(true)
+ val locationEnabled = MutableLiveData(true)
+
init {
bluetoothDeviceListHighRisk.value = mutableListOf()
bluetoothDeviceListLowRisk.value = mutableListOf()
bluetoothEnabled.value = BLEScanner.isBluetoothOn()
+ locationEnabled.value = LocationProvider.isLocationTurnedOn()
}
fun addScanResult(scanResult: ScanResult) = viewModelScope.launch(Dispatchers.IO) {
diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml
index 96ed13bd..82583497 100644
--- a/app/src/main/res/layout/fragment_scan.xml
+++ b/app/src/main/res/layout/fragment_scan.xml
@@ -150,7 +150,7 @@
android:layout_marginBottom="16dp"
android:clickable="true"
android:focusable="true"
- android:visibility="@{!vm.bluetoothEnabled ? View.GONE : View.VISIBLE, default=visible}"
+ android:visibility="@{!vm.bluetoothEnabled || !vm.locationEnabled ? View.GONE : View.VISIBLE, default=visible}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_baseline_stop_24"
@@ -168,5 +168,17 @@
android:visibility="@{vm.bluetoothEnabled ? View.GONE : View.VISIBLE, default=visible}"
/>
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/include_location_disabled.xml b/app/src/main/res/layout/include_location_disabled.xml
new file mode 100644
index 00000000..a2b18296
--- /dev/null
+++ b/app/src/main/res/layout/include_location_disabled.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 1615cf82..d9b11b91 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -254,7 +254,9 @@
Benachrichtigungen werden als Alarm mit hoher Priorität erstellt
Gefundene Chipolo 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
+ Standort Einstellungen öffnen
"Batterie Status: "
Voll
Mittel
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ff6c488f..36fd7917 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -284,7 +284,9 @@
Show Onboarding
Chipolo %s
Bluetooth is turned off. This app requires Bluetooth to scan for nearby trackers.
+ Location is turned off. This app requires the location permission to scan for nearby trackers. Please turn on location in your system settings.\n\nIt is possible that you have to restart the App for this to have an effect.
Open Bluetooth settings
+ Open Location settings
Battery Level:
Full
From e0f87ec45766604b60ad711046021411f289900a Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Sun, 19 May 2024 15:53:12 +0200
Subject: [PATCH 142/154] handle case if wakeLock cannot be held
---
.../detection/BackgroundBluetoothScanner.kt | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
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 527095a0..7725ce24 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
@@ -104,12 +104,17 @@ object BackgroundBluetoothScanner {
location = null
// Set a wake lock to keep the CPU running while we complete the scanning
- val wakeLock: PowerManager.WakeLock =
- (applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).run {
+ val powerManager = applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
+ val wakeLock: PowerManager.WakeLock? = powerManager.run {
+ try {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
- acquire(5*60*1000L /*5 minutes*/)
+ acquire(5 * 60 * 1000L /*5 minutes*/)
}
+ } catch (e: SecurityException) {
+ Timber.w("Failed to acquire wake lock: ${e.message}")
+ null
}
+ }
val useLocation = SharedPrefs.useLocationInTrackingDetection
if (useLocation) {
@@ -178,7 +183,7 @@ object BackgroundBluetoothScanner {
BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
// Release the wake lock when we are done
- wakeLock.release()
+ wakeLock?.release()
Timber.d("Finished Background Scan")
return BackgroundScanResults(
From f0652b8f4c6c669a53085eb5ecb5f0f99424f0a2 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 22 May 2024 12:16:13 +0200
Subject: [PATCH 143/154] upgrade gradle to 8.4.1.
---
build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/build.gradle b/build.gradle
index efddf3f4..7c6968a9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@ buildscript {
}
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.4.0'
+ classpath 'com.android.tools.build:gradle:8.4.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7"
From 802c86c853f42a178eedca0da7cdb38480ef282d Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 22 May 2024 14:04:26 +0200
Subject: [PATCH 144/154] avoid unnecessary duplicate calls of getDeviceType
---
.../database/models/device/BaseDevice.kt | 16 ++--
.../ui/scan/ScanDistanceFragment.kt | 77 ++++---------------
.../ui/scan/ScanResultWrapper.kt | 4 +-
3 files changed, 25 insertions(+), 72 deletions(-)
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 39e20939..464088a1 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
@@ -132,8 +132,8 @@ data class BaseDevice(
}
}
- fun getPublicKey(scanResult: ScanResult): String {
- return when (DeviceManager.getDeviceType(scanResult)) {
+ fun getPublicKey(scanResult: ScanResult, deviceType: DeviceType = DeviceManager.getDeviceType(scanResult)): String {
+ return when (deviceType) {
DeviceType.SAMSUNG,
DeviceType.GALAXY_SMART_TAG,
DeviceType.GALAXY_SMART_TAG_PLUS -> SamsungDevice.getPublicKey(scanResult)
@@ -141,8 +141,8 @@ data class BaseDevice(
}
}
- fun getConnectionState(scanResult: ScanResult): ConnectionState {
- return when (DeviceManager.getDeviceType(scanResult)) {
+ fun getConnectionState(scanResult: ScanResult, deviceType: DeviceType = DeviceManager.getDeviceType(scanResult)): ConnectionState {
+ return when (deviceType) {
DeviceType.TILE -> Tile.getConnectionState(scanResult)
DeviceType.CHIPOLO -> Chipolo.getConnectionState(scanResult)
DeviceType.SAMSUNG,
@@ -156,8 +156,8 @@ data class BaseDevice(
}
}
- fun getBatteryState(scanResult: ScanResult): BatteryState {
- return when (DeviceManager.getDeviceType(scanResult)) {
+ fun getBatteryState(scanResult: ScanResult, deviceType: DeviceType = DeviceManager.getDeviceType(scanResult)): BatteryState {
+ return when (deviceType) {
DeviceType.GALAXY_SMART_TAG,
DeviceType.GALAXY_SMART_TAG_PLUS -> SamsungDevice.getBatteryState(scanResult)
DeviceType.FIND_MY,
@@ -167,8 +167,8 @@ data class BaseDevice(
}
}
- fun getBatteryStateAsString(scanResult: ScanResult): String {
- return when (getBatteryState(scanResult)) {
+ fun getBatteryStateAsString(scanResult: ScanResult, deviceType: DeviceType = DeviceManager.getDeviceType(scanResult)): String {
+ return when (getBatteryState(scanResult, deviceType)) {
BatteryState.LOW -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.battery_low)
BatteryState.VERY_LOW -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.battery_very_low)
BatteryState.MEDIUM -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.battery_medium)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
index fac7f1da..1f624dfe 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
@@ -20,7 +20,6 @@ import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Compani
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getBatteryStateAsString
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getConnectionState
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice.Companion.getPublicKey
-import de.seemoo.at_tracking_detection.database.models.device.BatteryState
import de.seemoo.at_tracking_detection.database.models.device.ConnectionState
import de.seemoo.at_tracking_detection.database.models.device.DeviceManager
import de.seemoo.at_tracking_detection.database.models.device.DeviceType
@@ -34,6 +33,7 @@ class ScanDistanceFragment : Fragment() {
private val safeArgs: ScanDistanceFragmentArgs by navArgs()
private var deviceAddress: String? = null
+ private var deviceType: DeviceType? = null
private var oldAnimationValue = 0f
private val animationDuration = 1000L
@@ -43,37 +43,38 @@ class ScanDistanceFragment : Fragment() {
private val scanCallback: ScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult?) {
super.onScanResult(callbackType, result)
- result?.let {
+ result?.let {scanResult ->
val publicKey = safeArgs.deviceAddress
if (publicKey == null) {
showSearchMessage()
}
- if (getPublicKey(it) == publicKey){
- // viewModel.bluetoothRssi.postValue(it.rssi)
- val connectionState = getConnectionState(it)
+ if (deviceType == null) {
+ deviceType = DeviceManager.getDeviceType(scanResult)
+ }
+ if (getPublicKey(scanResult, deviceType!!) == publicKey){
+ val connectionState = getConnectionState(scanResult, deviceType!!)
viewModel.connectionState.postValue(connectionState)
- val deviceType = DeviceManager.getDeviceType(it)
- val connectionStateString = getConnectionStateExplanation(connectionState, deviceType)
+ val connectionStateString = getConnectionStateExplanation(connectionState, deviceType!!)
viewModel.connectionStateString.postValue(connectionStateString)
- val batteryState = getBatteryState(it)
- val batteryStateString = getBatteryStateAsString(it)
+ val batteryState = getBatteryState(scanResult, deviceType!!)
+ val batteryStateString = getBatteryStateAsString(scanResult, deviceType!!)
viewModel.batteryStateString.postValue(batteryStateString)
viewModel.batteryState.postValue(batteryState)
- val connectionQuality = Utility.dbmToPercent(it.rssi).toFloat()
+ val connectionQuality = Utility.dbmToPercent(scanResult.rssi).toFloat()
val displayedConnectionQuality = (connectionQuality * 100).toInt()
viewModel.connectionQuality.postValue(displayedConnectionQuality)
- binding.deviceTypeText.text = DeviceType.userReadableName(deviceType)
+ binding.deviceTypeText.text = DeviceType.userReadableName(deviceType!!)
// setBattery(requireContext(), batteryState)
setHeight(connectionQuality)
if (viewModel.isFirstScanCallback.value as Boolean) {
viewModel.isFirstScanCallback.value = false
- removeSearchMessage(batteryState != BatteryState.UNKNOWN)
+ removeSearchMessage()
}
}
@@ -89,12 +90,12 @@ class ScanDistanceFragment : Fragment() {
it,
R.string.ble_service_connection_error,
Snackbar.LENGTH_LONG
- )
+ ).show()
}
}
}
- private fun removeSearchMessage(showBattery: Boolean = true) {
+ private fun removeSearchMessage() {
binding.scanResultLoadingBar.visibility = View.GONE
binding.searchingForDevice.visibility = View.GONE
binding.connectionQuality.visibility = View.VISIBLE
@@ -102,18 +103,12 @@ class ScanDistanceFragment : Fragment() {
binding.connectionStateLayout.visibility = View.VISIBLE
binding.scanExplanationLayout.visibility = View.VISIBLE
binding.deviceNotFound.visibility = View.GONE
-// if (showBattery) {
-// binding.batteryLayout.visibility = View.VISIBLE
-// } else {
-// binding.batteryLayout.visibility = View.GONE
-// }
}
private fun showSearchMessage() {
binding.scanResultLoadingBar.visibility = View.VISIBLE
binding.searchingForDevice.visibility = View.VISIBLE
binding.connectionQuality.visibility = View.GONE
- // binding.batteryLayout.visibility = View.GONE
binding.scanExplanationLayout.visibility = View.GONE
binding.deviceTypeLayout.visibility = View.GONE
binding.connectionStateLayout.visibility = View.GONE
@@ -125,7 +120,6 @@ class ScanDistanceFragment : Fragment() {
binding.searchingForDevice.visibility = View.GONE
binding.connectionQuality.visibility = View.GONE
binding.scanExplanationLayout.visibility = View.GONE
- // binding.batteryLayout.visibility = View.GONE
binding.deviceTypeLayout.visibility = View.GONE
binding.connectionStateLayout.visibility = View.GONE
binding.deviceNotFound.visibility = View.VISIBLE
@@ -153,17 +147,6 @@ class ScanDistanceFragment : Fragment() {
}
}
-// private fun setBattery(context: Context, batteryState: BatteryState) {
-// binding.batteryLayout.visibility = View.VISIBLE
-// when(batteryState) {
-// BatteryState.FULL -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_full_24))
-// BatteryState.MEDIUM -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_medium_24))
-// BatteryState.LOW -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_low_24))
-// BatteryState.VERY_LOW -> binding.batterySymbol.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_battery_very_low_24))
-// else -> binding.batteryLayout.visibility = View.GONE
-// }
-// }
-
private fun getConnectionStateExplanation(connectionState: ConnectionState, deviceType: DeviceType): String {
return when (connectionState) {
ConnectionState.OVERMATURE_OFFLINE -> when(deviceType) {
@@ -231,36 +214,6 @@ class ScanDistanceFragment : Fragment() {
startBluetoothScan()
-// val infoButton = binding.infoButton
-// infoButton.setOnClickListener {
-// val text = when (viewModel.connectionState.value as ConnectionState){
-// ConnectionState.OVERMATURE_OFFLINE -> R.string.connection_state_overmature_offline_explanation
-// ConnectionState.CONNECTED -> R.string.connection_state_connected_explanation
-// ConnectionState.OFFLINE -> R.string.connection_state_offline_explanation
-// ConnectionState.PREMATURE_OFFLINE -> R.string.connection_state_premature_offline_explanation
-// ConnectionState.UNKNOWN -> R.string.connection_state_unknown_explanation
-// }
-// val duration = Toast.LENGTH_SHORT
-//
-// val toast = Toast.makeText(requireContext(), text, duration) // in Activity
-// toast.show()
-// }
-
-// val batterySymbol = binding.batterySymbol
-// batterySymbol.setOnClickListener {
-// val text = when (viewModel.batteryState.value as BatteryState){
-// BatteryState.FULL -> R.string.battery_full
-// BatteryState.MEDIUM -> R.string.battery_medium
-// BatteryState.VERY_LOW -> R.string.battery_very_low
-// BatteryState.LOW -> R.string.battery_low
-// else -> R.string.battery_unknown
-// }
-// val duration = Toast.LENGTH_SHORT
-//
-// val toast = Toast.makeText(requireContext(), text, duration) // in Activity
-// toast.show()
-// }
-
return binding.root
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt
index 4bc01d67..383a86f5 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanResultWrapper.kt
@@ -12,9 +12,9 @@ data class ScanResultWrapper(val scanResult: ScanResult){
var rssiValue: Int = scanResult.rssi
var txPower: Int = scanResult.txPower
var isConnectable: Boolean = scanResult.isConnectable
- val uniqueIdentifier = getPublicKey(scanResult) // either public key or MAC-Address
val deviceType = getDeviceType(scanResult)
- var connectionState = getConnectionState(scanResult)
+ val uniqueIdentifier = getPublicKey(scanResult, deviceType) // either public key or MAC-Address
+ var connectionState = getConnectionState(scanResult, deviceType)
val serviceUuids = scanResult.scanRecord?.serviceUuids?.map { it.toString() }?.toList()
val mfg = scanResult.scanRecord?.bytes
From c220f94c1f8ab259830e1489b7144672c4632b6a Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 22 May 2024 14:12:51 +0200
Subject: [PATCH 145/154] low risk "secure tracker" explanation in manual scan
is hidden if there are now low risk trackers
---
.../at_tracking_detection/ui/scan/ScanViewModel.kt | 10 ++++++++++
app/src/main/res/layout/fragment_scan.xml | 2 ++
2 files changed, 12 insertions(+)
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 5890f904..e63f66c6 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
@@ -134,4 +134,14 @@ class ScanViewModel @Inject constructor(
addSource(bluetoothDeviceListHighRisk) { update() }
addSource(bluetoothDeviceListLowRisk) { update() }
}
+
+ val lowRiskIsEmpty: LiveData = MediatorLiveData().apply {
+ // Function to update the isListEmpty LiveData
+ fun update() {
+ val lowRiskEmpty = bluetoothDeviceListLowRisk.value?.isEmpty() ?: true
+ this.value = lowRiskEmpty
+ }
+
+ addSource(bluetoothDeviceListLowRisk) { update() }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml
index 82583497..288f97c4 100644
--- a/app/src/main/res/layout/fragment_scan.xml
+++ b/app/src/main/res/layout/fragment_scan.xml
@@ -72,6 +72,8 @@
From 7c442e4bf912991c31429956b9801b88d24d77f8 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 22 May 2024 14:39:48 +0200
Subject: [PATCH 146/154] Show Website URL also for Low Risk Devices
---
.../database/models/device/DeviceManager.kt | 32 ++++++++++++++---
.../ui/tracking/TrackingViewModel.kt | 36 ++++++++++++-------
2 files changed, 51 insertions(+), 17 deletions(-)
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 60a0d778..fedff9c6 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
@@ -21,13 +21,22 @@ object DeviceManager {
val deviceAddress = scanResult.device.address
// Check cache, before calculating again
+ var deviceType: DeviceType? = getDeviceTypeFromCache(deviceAddress)
+ if (deviceType != null) {
+ return deviceType
+ } else {
+ Timber.d("Device type not in cache, calculating...")
+ deviceType = calculateDeviceType(scanResult)
+ deviceTypeCache[deviceAddress] = deviceType
+ return deviceType
+ }
+ }
+
+ fun getDeviceTypeFromCache(deviceAddress: String): DeviceType? {
deviceTypeCache[deviceAddress]?.let { cachedDeviceType ->
return cachedDeviceType
}
-
- val deviceType = calculateDeviceType(scanResult)
- deviceTypeCache[deviceAddress] = deviceType
- return deviceType
+ return null
}
private fun calculateDeviceType(scanResult: ScanResult): DeviceType {
@@ -59,6 +68,21 @@ object DeviceManager {
return Unknown.deviceType
}
+ fun getWebsiteURL(deviceType: DeviceType): String {
+ return when (deviceType) {
+ DeviceType.UNKNOWN -> Unknown.websiteManufacturer
+ DeviceType.AIRTAG -> AirTag.websiteManufacturer
+ DeviceType.APPLE -> AppleDevice.websiteManufacturer
+ DeviceType.AIRPODS -> AirPods.websiteManufacturer
+ DeviceType.TILE -> Tile.websiteManufacturer
+ DeviceType.FIND_MY -> FindMy.websiteManufacturer
+ DeviceType.CHIPOLO -> Chipolo.websiteManufacturer
+ DeviceType.SAMSUNG -> SamsungDevice.websiteManufacturer
+ DeviceType.GALAXY_SMART_TAG -> SmartTag.websiteManufacturer
+ DeviceType.GALAXY_SMART_TAG_PLUS -> SmartTagPlus.websiteManufacturer
+ }
+ }
+
val scanFilter: List = devices.map { it.bluetoothFilter }
val gattIntentFilter: IntentFilter = IntentFilter().apply {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
index 706a2a27..b6583615 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
@@ -6,6 +6,7 @@ import androidx.lifecycle.*
import de.seemoo.at_tracking_detection.database.models.Beacon
import de.seemoo.at_tracking_detection.database.models.device.BaseDevice
import de.seemoo.at_tracking_detection.database.models.device.Connectable
+import de.seemoo.at_tracking_detection.database.models.device.DeviceManager
import de.seemoo.at_tracking_detection.database.models.device.DeviceType
import de.seemoo.at_tracking_detection.database.repository.BeaconRepository
import de.seemoo.at_tracking_detection.database.repository.DeviceRepository
@@ -27,7 +28,7 @@ class TrackingViewModel @Inject constructor(
val noLocationsYet = MutableLiveData(true)
- val manufacturerWebsiteUrl = MutableLiveData("https://www.apple.com/airtag/")
+ val manufacturerWebsiteUrl = MutableLiveData()
var deviceType = MutableLiveData(DeviceType.UNKNOWN)
@@ -64,24 +65,33 @@ class TrackingViewModel @Inject constructor(
}
fun loadDevice(address: String) =
- deviceRepository.getDevice(address).also { it ->
- device.postValue(it)
- if (it != null) {
- val deviceObserved = it.nextObservationNotification != null && it.nextObservationNotification!!.isAfter(
+ deviceRepository.getDevice(address).also { device ->
+ this.device.postValue(device)
+
+ deviceType.value = DeviceManager.getDeviceTypeFromCache(address) ?: DeviceType.UNKNOWN
+ Timber.d("Set Device type: ${deviceType.value}")
+
+ if (device != null) {
+ if (deviceType.value == null) {
+ deviceType.value = device.device.deviceContext.deviceType
+ }
+ val deviceObserved = device.nextObservationNotification != null && device.nextObservationNotification!!.isAfter(
LocalDateTime.now())
trackerObserved.postValue(deviceObserved)
- deviceIgnored.postValue(it.ignore)
+ deviceIgnored.postValue(device.ignore)
noLocationsYet.postValue(false)
- connectable.postValue(it.device is Connectable)
- showNfcHint.postValue(it.deviceType == DeviceType.AIRTAG)
- manufacturerWebsiteUrl.postValue(it.device.deviceContext.websiteManufacturer)
- deviceType.postValue(it.device.deviceContext.deviceType)
- Timber.d("Set Device type: ${it.device.deviceContext.deviceType}")
- canBeIgnored.postValue(it.device.deviceContext.deviceType.canBeIgnored())
- val notification = notificationRepository.notificationForDevice(it).firstOrNull()
+ connectable.postValue(device.device is Connectable)
+ canBeIgnored.postValue(device.device.deviceContext.deviceType.canBeIgnored())
+ val notification = notificationRepository.notificationForDevice(device).firstOrNull()
notification?.let { notificationId.postValue(it.notificationId) }
} else {
noLocationsYet.postValue(true)
+ }
+ showNfcHint.postValue(deviceType.value == DeviceType.AIRTAG)
+ if (deviceType.value != null) {
+ val websiteURL = DeviceManager.getWebsiteURL(deviceType.value!!)
+ manufacturerWebsiteUrl.postValue(websiteURL)
+ } else {
manufacturerWebsiteUrl.postValue("")
}
}
From de976509be202f887bbcf341389863d1d48b3e74 Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Wed, 22 May 2024 17:05:03 +0200
Subject: [PATCH 147/154] Fix DeviceType Detection and Display of Explanation
for it for unknown devices
---
.../at_tracking_detection/ui/tracking/TrackingViewModel.kt | 6 ++----
.../java/de/seemoo/at_tracking_detection/util/Utility.kt | 3 +--
app/src/main/res/values-de/strings.xml | 1 +
app/src/main/res/values/strings.xml | 1 +
4 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
index b6583615..2651d0bc 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
@@ -72,16 +72,14 @@ class TrackingViewModel @Inject constructor(
Timber.d("Set Device type: ${deviceType.value}")
if (device != null) {
- if (deviceType.value == null) {
- deviceType.value = device.device.deviceContext.deviceType
- }
+ deviceType.value = device.device.deviceContext.deviceType
val deviceObserved = device.nextObservationNotification != null && device.nextObservationNotification!!.isAfter(
LocalDateTime.now())
trackerObserved.postValue(deviceObserved)
deviceIgnored.postValue(device.ignore)
noLocationsYet.postValue(false)
connectable.postValue(device.device is Connectable)
- canBeIgnored.postValue(device.device.deviceContext.deviceType.canBeIgnored())
+ canBeIgnored.postValue(deviceType.value!!.canBeIgnored())
val notification = notificationRepository.notificationForDevice(device).firstOrNull()
notification?.let { notificationId.postValue(it.notificationId) }
} else {
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 e83322ad..a1e2c4e1 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
@@ -31,7 +31,6 @@ import org.osmdroid.views.overlay.CopyrightOverlay
import org.osmdroid.views.overlay.Marker
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
import timber.log.Timber
-import java.security.Permission
object Utility {
@@ -250,7 +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)
- else -> ""
+ else -> ATTrackingDetectionApplication.getAppContext().resources.getString(R.string.explanation_unknown)
}
}
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index d9b11b91..b09990ea 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -345,6 +345,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:\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.
Zeigt Erklärtext
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 36fd7917..a5293651 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -377,6 +377,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:\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.
The device name cannot be empty
From 4893218846c94b5c45c75dc1a63fa3223ccf10ed Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 30 May 2024 12:39:50 +0200
Subject: [PATCH 148/154] fix bug in ScanDistance
---
.../ui/scan/ScanDistanceFragment.kt | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
index 1f624dfe..6819d11f 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/ScanDistanceFragment.kt
@@ -44,16 +44,17 @@ class ScanDistanceFragment : Fragment() {
override fun onScanResult(callbackType: Int, result: ScanResult?) {
super.onScanResult(callbackType, result)
result?.let {scanResult ->
- val publicKey = safeArgs.deviceAddress
+ val filteredIdentifier = safeArgs.deviceAddress
- if (publicKey == null) {
+ if (filteredIdentifier == null) {
showSearchMessage()
}
- if (deviceType == null) {
- deviceType = DeviceManager.getDeviceType(scanResult)
- }
- if (getPublicKey(scanResult, deviceType!!) == publicKey){
+ if (getPublicKey(scanResult) == filteredIdentifier){
+ if (deviceType == null) {
+ deviceType = DeviceManager.getDeviceType(scanResult)
+ }
+
val connectionState = getConnectionState(scanResult, deviceType!!)
viewModel.connectionState.postValue(connectionState)
val connectionStateString = getConnectionStateExplanation(connectionState, deviceType!!)
From c2af3d7278519fac560308ce6ebd3041154a244f Mon Sep 17 00:00:00 2001
From: Dennis Arndt
Date: Thu, 30 May 2024 13:16:23 +0200
Subject: [PATCH 149/154] fix bug in fetching deviceType from safe trackers
---
.../database/models/device/DeviceManager.kt | 8 ++++++++
.../ui/TrackingNotificationActivity.kt | 5 ++++-
.../ui/scan/BluetoothDeviceAdapter.kt | 10 +++++++++-
.../ui/tracking/TrackingFragment.kt | 5 ++++-
.../ui/tracking/TrackingViewModel.kt | 12 +++---------
app/src/main/res/navigation/main_navigation.xml | 4 ++++
6 files changed, 32 insertions(+), 12 deletions(-)
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 fedff9c6..3573278b 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
@@ -83,6 +83,14 @@ object DeviceManager {
}
}
+ fun deviceTypeToString(deviceType: DeviceType): String {
+ return deviceType.name
+ }
+
+ fun stringToDeviceType(deviceTypeString: String): DeviceType {
+ return DeviceType.valueOf(deviceTypeString)
+ }
+
val scanFilter: List = devices.map { it.bluetoothFilter }
val gattIntentFilter: IntentFilter = IntentFilter().apply {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/TrackingNotificationActivity.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/TrackingNotificationActivity.kt
index c8898412..4a4577fc 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/TrackingNotificationActivity.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/TrackingNotificationActivity.kt
@@ -30,7 +30,10 @@ class TrackingNotificationActivity : AppCompatActivity() {
Timber.e("Device address is needed! Going home...")
this.onSupportNavigateUp()
} else {
- val args = TrackingFragmentArgs(deviceAddress, notificationId).toBundle()
+ val args = TrackingFragmentArgs(
+ deviceAddress = deviceAddress,
+ notificationId = notificationId
+ ).toBundle()
navController.setGraph(R.navigation.tracking_navigation, args)
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
index f202c147..4ef4cd2f 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/scan/BluetoothDeviceAdapter.kt
@@ -8,6 +8,8 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.card.MaterialCardView
import de.seemoo.at_tracking_detection.R
+import de.seemoo.at_tracking_detection.database.models.device.DeviceManager
+import de.seemoo.at_tracking_detection.database.models.device.DeviceType
import de.seemoo.at_tracking_detection.databinding.ItemScanResultBinding
import timber.log.Timber
@@ -38,7 +40,13 @@ class BluetoothDeviceAdapter:
.setOnClickListener {
try {
val deviceAddress: String = wrappedScanResult.uniqueIdentifier
- val directions = ScanFragmentDirections.actionScanToTrackingFragment(deviceAddress)
+ val deviceType: DeviceType = wrappedScanResult.deviceType
+ val deviceTypeString = DeviceManager.deviceTypeToString(deviceType)
+
+ val directions = ScanFragmentDirections.actionScanToTrackingFragment(
+ deviceAddress = deviceAddress,
+ deviceTypeAsString = deviceTypeString
+ )
holder.itemView.findNavController()
.navigate(directions)
} catch (e: Exception) {
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
index 3f4cedb5..14c1a5b4 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingFragment.kt
@@ -31,6 +31,7 @@ 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.DeviceManager
+import de.seemoo.at_tracking_detection.database.models.device.DeviceType
import de.seemoo.at_tracking_detection.databinding.FragmentTrackingBinding
import de.seemoo.at_tracking_detection.ui.MainActivity
import de.seemoo.at_tracking_detection.util.Utility
@@ -69,9 +70,11 @@ class TrackingFragment : Fragment() {
val notificationId = safeArgs.notificationId
// This is called deviceAddress but contains the ID not necessarily the address
val deviceAddress = safeArgs.deviceAddress
+ val deviceTypeAsString = safeArgs.deviceTypeAsString
+ val deviceType: DeviceType = DeviceManager.stringToDeviceType(deviceTypeAsString)
trackingViewModel.notificationId.postValue(notificationId)
trackingViewModel.deviceAddress.postValue(deviceAddress)
- trackingViewModel.loadDevice(safeArgs.deviceAddress)
+ trackingViewModel.loadDevice(safeArgs.deviceAddress, deviceType)
trackingViewModel.notificationId.observe(viewLifecycleOwner) {
this.notificationId = it
}
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
index 2651d0bc..6512f82e 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ui/tracking/TrackingViewModel.kt
@@ -54,25 +54,19 @@ class TrackingViewModel @Inject constructor(
beaconRepository.getDeviceBeacons(it)
}
- val beaconsHaveMissingLocation: LiveData = markerLocations.map {
- it.any { beacon ->
- beacon.locationId == null
- }
- }
-
val amountBeacons: LiveData = markerLocations.map {
it.size.toString()
}
- fun loadDevice(address: String) =
+ fun loadDevice(address: String, deviceTypeOverride: DeviceType) =
deviceRepository.getDevice(address).also { device ->
this.device.postValue(device)
+ deviceType.value = deviceTypeOverride
- deviceType.value = DeviceManager.getDeviceTypeFromCache(address) ?: DeviceType.UNKNOWN
Timber.d("Set Device type: ${deviceType.value}")
if (device != null) {
- deviceType.value = device.device.deviceContext.deviceType
+ deviceType.value = device.device.deviceContext.deviceType // This line is still necessary for the Device List in Expert Mode
val deviceObserved = device.nextObservationNotification != null && device.nextObservationNotification!!.isAfter(
LocalDateTime.now())
trackerObserved.postValue(deviceObserved)
diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml
index 20d27adc..cbe8d53c 100644
--- a/app/src/main/res/navigation/main_navigation.xml
+++ b/app/src/main/res/navigation/main_navigation.xml
@@ -241,6 +241,10 @@
+
Date: Thu, 30 May 2024 13:40:47 +0200
Subject: [PATCH 150/154] fix compilation bug
---
app/src/main/res/navigation/tracking_navigation.xml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/app/src/main/res/navigation/tracking_navigation.xml b/app/src/main/res/navigation/tracking_navigation.xml
index 0fac03c6..a171404c 100644
--- a/app/src/main/res/navigation/tracking_navigation.xml
+++ b/app/src/main/res/navigation/tracking_navigation.xml
@@ -39,6 +39,10 @@
+
Date: Mon, 10 Jun 2024 13:55:46 +0200
Subject: [PATCH 151/154] Adopting changes to set alarms more reliably
---
.../worker/BackgroundWorkScheduler.kt | 148 ++++++++++++++----
1 file changed, 114 insertions(+), 34 deletions(-)
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
index fd6358c5..04a1f598 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/BackgroundWorkScheduler.kt
@@ -1,6 +1,5 @@
package de.seemoo.at_tracking_detection.worker
-import android.annotation.SuppressLint
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
@@ -19,6 +18,8 @@ import javax.inject.Inject
import javax.inject.Singleton
import android.Manifest
import de.seemoo.at_tracking_detection.util.SharedPrefs
+import java.time.Instant
+import java.util.TimeZone
@Singleton
class BackgroundWorkScheduler @Inject constructor(
@@ -105,20 +106,35 @@ class BackgroundWorkScheduler @Inject constructor(
// Run in 60minBack
val timeInMillisUntilNotification: Long = 60 * 60 * 1000
- val alarmDate = LocalDateTime.now().plus(timeInMillisUntilNotification, ChronoUnit.MILLIS)
+ val alarmDate =
+ LocalDateTime.now().plus(timeInMillisUntilNotification, ChronoUnit.MILLIS)
val alarmTime = System.currentTimeMillis() + timeInMillisUntilNotification
- val intent = Intent(ATTrackingDetectionApplication.getAppContext(), ScheduleWorkersReceiver::class.java)
+ val intent = Intent(
+ ATTrackingDetectionApplication.getAppContext(),
+ ScheduleWorkersReceiver::class.java
+ )
intent.action = "AlarmManagerWakeUp_Schedule_BackgroundScan"
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
- PendingIntent.getBroadcast(ATTrackingDetectionApplication.getAppContext(), -103,intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
- }else {
- PendingIntent.getBroadcast(ATTrackingDetectionApplication.getAppContext(), -103, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ PendingIntent.getBroadcast(
+ ATTrackingDetectionApplication.getAppContext(),
+ -103,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+ } else {
+ PendingIntent.getBroadcast(
+ ATTrackingDetectionApplication.getAppContext(),
+ -103,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ )
}
val alarmManager = ATTrackingDetectionApplication.getAppContext().getSystemService(
- Context.ALARM_SERVICE) as AlarmManager
+ Context.ALARM_SERVICE
+ ) as AlarmManager
alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
Timber.d("Scheduled an alarm to reschedule the scan at $alarmDate")
@@ -135,45 +151,109 @@ class BackgroundWorkScheduler @Inject constructor(
val alarmDate = LocalDateTime.now().plus(timeInMillisUntilNotification, ChronoUnit.MILLIS)
val alarmTime = System.currentTimeMillis() + timeInMillisUntilNotification
- val intent = Intent(ATTrackingDetectionApplication.getAppContext(), ScheduleWorkersReceiver::class.java)
+ val intent = Intent(
+ ATTrackingDetectionApplication.getAppContext(),
+ ScheduleWorkersReceiver::class.java
+ )
intent.action = "AlarmManagerWakeUp_Perform_BackgroundScan"
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
- PendingIntent.getBroadcast(ATTrackingDetectionApplication.getAppContext(), -103,intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
- }else {
- PendingIntent.getBroadcast(ATTrackingDetectionApplication.getAppContext(), -103, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ PendingIntent.getBroadcast(
+ ATTrackingDetectionApplication.getAppContext(),
+ -103,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+ } else {
+ PendingIntent.getBroadcast(
+ ATTrackingDetectionApplication.getAppContext(),
+ -103,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ )
}
val alarmManager = ATTrackingDetectionApplication.getAppContext().getSystemService(
- Context.ALARM_SERVICE) as AlarmManager
+ Context.ALARM_SERVICE
+ ) as AlarmManager
// We use exact Alarms since we want regular background scans to happen.
- if (Utility.checkPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)) {
- try {
- alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
- Timber.d("Scheduled an setExactAndAllowWhileIdle alarm to start a scan at $alarmDate")
- }catch (exception: SecurityException) {
- try {
- Timber.w("Failed scheduling setExactAndAllowWhileIdle Alarm scan")
- // Alarm could not be scheduled because user disallowed battery exception
- alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
- Timber.d("Scheduled an setExact alarm to start a scan at $alarmDate")
- }catch (exception: SecurityException) {
- Timber.w("Failed scheduling setExact Alarm scan")
- // Alarm could not be scheduled because user disallowed battery exception
- alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
- Timber.d("Scheduled an set alarm to start a scan at $alarmDate")
- }
- } catch (exception: Exception) {
- Timber.e("Unexpected exception while scheduling alarm: ${exception.message}")
- }
+ if (Build.VERSION.SDK_INT >= 31 && alarmManager.canScheduleExactAlarms()) {
+ scheduleAlarmExactAndAllowWhileIdle(alarmManager, alarmTime, alarmDate, pendingIntent)
} else {
- // Alarm could not be scheduled because user disallowed battery exception
- alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
- Timber.d("Scheduled an set alarm to start a scan at $alarmDate")
+ Timber.e("Permission to schedule exact alarm not given. Scheduling normal alarm")
+ scheduleAlarm(alarmManager, alarmTime, alarmDate, pendingIntent)
}
SharedPrefs.nextScanDate = alarmDate
}
+
+
+ fun scheduleAlarmExactAndAllowWhileIdle(
+ alarmManager: AlarmManager,
+ alarmTime: Long,
+ alarmDate: LocalDateTime,
+ pendingIntent: PendingIntent
+ ) {
+
+ try {
+ alarmManager.setExactAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP,
+ alarmTime,
+ pendingIntent
+ )
+ Timber.d(
+ "Scheduled an setExactAndAllowWhileIdle alarm to start a scan at $alarmDate"
+ )
+ } catch (exception: SecurityException) {
+ Timber.w("Failed scheduling setExactAndAllowWhileIdle Alarm scan $exception")
+ scheduleAlarmExact(alarmManager, alarmTime, alarmDate, pendingIntent)
+ } catch (exception: Exception) {
+ Timber.w("Failed scheduling setExactAndAllowWhileIdle Alarm scan $exception")
+ scheduleAlarmExact(alarmManager, alarmTime, alarmDate, pendingIntent)
+ }
+ }
+
+ fun scheduleAlarmExact(
+ alarmManager: AlarmManager,
+ alarmTime: Long,
+ alarmDate: LocalDateTime,
+ pendingIntent: PendingIntent
+ ) {
+ try {
+ // Alarm could not be scheduled because user disallowed battery exception
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ Timber.d(
+ "Scheduled an setExact alarm to start a scan at $alarmDate"
+ )
+ } catch (exception: SecurityException) {
+ Timber.w("Failed scheduling setExact Alarm scan: $exception")
+ scheduleAlarm(alarmManager, alarmTime, alarmDate, pendingIntent)
+ } catch (exception: Exception) {
+ Timber.w("Failed scheduling setExact Alarm scan: $exception")
+ scheduleAlarm(alarmManager, alarmTime, alarmDate, pendingIntent)
+ }
+ }
+
+ fun scheduleAlarm(
+ alarmManager: AlarmManager,
+ alarmTime: Long,
+ alarmDate: LocalDateTime,
+ pendingIntent: PendingIntent
+ ) {
+ try {
+ // Alarm could not be scheduled because user disallowed battery exception
+ alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent)
+ Timber.d(
+ "Scheduled an set alarm to start a scan at $alarmDate"
+ )
+ } catch (exception: SecurityException) {
+ Timber.e("Failed to schedule any kind of alarm. $exception\nFalling back to old method.")
+ ATTrackingDetectionApplication.getCurrentApp().backgroundWorkScheduler.legacyLaunch()
+ } catch (exception: Exception) {
+ Timber.e("Failed to schedule any kind of alarm. $exception\nFalling back to old method.")
+ ATTrackingDetectionApplication.getCurrentApp().backgroundWorkScheduler.legacyLaunch()
+ }
+ }
}
}
\ No newline at end of file
From a4e5c835034b304128716b136c0b86de860aa394 Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Mon, 10 Jun 2024 14:32:40 +0200
Subject: [PATCH 152/154] Removing second launch icon
---
app/src/main/AndroidManifest.xml | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7d2be2ca..a030e28e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -38,8 +38,13 @@
+
-
+
+
+
-
-
-
-
-
+
Date: Mon, 10 Jun 2024 14:33:25 +0200
Subject: [PATCH 153/154] Adding Scheduling alarms with new permission for
Android 13 and Android 14
---
app/build.gradle | 2 +-
.../ATTrackingDetectionApplication.kt | 15 +++++++++++++++
.../SetExactAlarmPermissionChangedReceiver.kt | 17 +++++++++++++++++
3 files changed, 33 insertions(+), 1 deletion(-)
create mode 100644 app/src/main/java/de/seemoo/at_tracking_detection/worker/SetExactAlarmPermissionChangedReceiver.kt
diff --git a/app/build.gradle b/app/build.gradle
index 0e023d98..f2fadaca 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -26,7 +26,7 @@ android {
applicationId "de.seemoo.at_tracking_detection"
minSdkVersion 28
targetSdk = 34
- versionCode 41
+ versionCode 44
versionName "2.2"
buildConfigField "String", "API_KEY", apiProperties["API_KEY"]
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt b/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt
index e31d702c..6dafe4c5 100644
--- a/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/ATTrackingDetectionApplication.kt
@@ -2,9 +2,11 @@ package de.seemoo.at_tracking_detection
import android.Manifest
import android.app.Activity
+import android.app.AlarmManager
import android.app.Application
import android.content.Context
import android.content.Intent
+import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.os.Build
@@ -26,6 +28,7 @@ import de.seemoo.at_tracking_detection.util.ATTDLifecycleCallbacks
import de.seemoo.at_tracking_detection.util.SharedPrefs
import de.seemoo.at_tracking_detection.util.Utility
import de.seemoo.at_tracking_detection.worker.BackgroundWorkScheduler
+import de.seemoo.at_tracking_detection.worker.SetExactAlarmPermissionChangedReceiver
import fr.bipi.tressence.file.FileLoggerTree
import timber.log.Timber
import java.io.File
@@ -120,6 +123,8 @@ class ATTrackingDetectionApplication : Application(), Configuration.Provider {
notificationService.scheduleSurveyNotification(false)
BackgroundWorkScheduler.scheduleAlarmWakeupIfScansFail()
+ registerBroadcastReceiver()
+
if (BuildConfig.DEBUG) {
// // Get a location for testing
// Timber.d("Request location")
@@ -171,6 +176,16 @@ class ATTrackingDetectionApplication : Application(), Configuration.Provider {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
})
+ private fun registerBroadcastReceiver() {
+ if (Build.VERSION.SDK_INT >= 31) {
+ val br = SetExactAlarmPermissionChangedReceiver()
+ val filter =
+ IntentFilter(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED)
+ val flags = ContextCompat.RECEIVER_NOT_EXPORTED
+ ContextCompat.registerReceiver(this, br, filter, flags)
+ }
+ }
+
companion object {
private lateinit var instance: ATTrackingDetectionApplication
fun getAppContext(): Context = instance.applicationContext
diff --git a/app/src/main/java/de/seemoo/at_tracking_detection/worker/SetExactAlarmPermissionChangedReceiver.kt b/app/src/main/java/de/seemoo/at_tracking_detection/worker/SetExactAlarmPermissionChangedReceiver.kt
new file mode 100644
index 00000000..836f0de7
--- /dev/null
+++ b/app/src/main/java/de/seemoo/at_tracking_detection/worker/SetExactAlarmPermissionChangedReceiver.kt
@@ -0,0 +1,17 @@
+package de.seemoo.at_tracking_detection.worker
+
+import android.app.AlarmManager
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import de.seemoo.at_tracking_detection.ATTrackingDetectionApplication
+import timber.log.Timber
+
+class SetExactAlarmPermissionChangedReceiver: BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (intent?.action == AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED) {
+ Timber.d("Received AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED")
+ ATTrackingDetectionApplication.getCurrentApp().backgroundWorkScheduler.launch()
+ }
+ }
+}
\ No newline at end of file
From d974f6c093ef1e25da8cc1735aeb93c52cc1379b Mon Sep 17 00:00:00 2001
From: Alexander Heinrich
Date: Tue, 11 Jun 2024 12:03:30 +0200
Subject: [PATCH 154/154] F-Droid 2.2
---
api.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/api.properties b/api.properties
index 5ce2e9b8..157eb729 100644
--- a/api.properties
+++ b/api.properties
@@ -1,2 +1,2 @@
-API_KEY="UCKgXwP.Dvmxyn1UygvnJfy2DV16OjbmHia4xXpd"
+API_KEY="AGdk9qPF.Bl9yatnjjoy9WEEjXYGVewE7ZyJET9Yy"
API_BASE_ADDRESS="https://tpe.seemoo.tu-darmstadt.de/api/"
\ No newline at end of file