Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show all devices #4

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -24,21 +23,36 @@ open class LocationProvider @Inject constructor(

private val locationRequesters = ArrayList<LocationRequester>()

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
}

return getLastLocationFromAnyProvider(checkRequirements)
}

/**
* 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
}

Expand All @@ -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 {
Expand All @@ -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
}

Expand All @@ -162,89 +180,73 @@ 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,
handler.looper
)
}

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)
}

Expand Down Expand Up @@ -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) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ interface Api {
@Header("User-Agent") userAgent: String = USER_AGENT
): Response<Token>

/**
* Deletes the study data related to the given token.
*/
@Headers("Authorization: Api-Key $API_KEY")
@DELETE("delete_study_data")
suspend fun deleteStudyData(
@Header("token") token: String,
@Header("X-Timezone") timezone: String = TIME_ZONE,
@Header("User-Agent") userAgent: String = USER_AGENT
): Response<Void>

@GET("ping")
suspend fun ping(
@Header("X-Timezone") timezone: String = TIME_ZONE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,7 @@ class OnboardingActivity : AppIntro() {

notificationSlide(slideNumber + 3)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
addSlide(IgnoreBatteryOptimizationFragment.newInstance())
}
addSlide(IgnoreBatteryOptimizationFragment.newInstance())

addSlide(ShareDataFragment.newInstance())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,5 @@ class DeviceMapFragment : Fragment() {
}
}
}


}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ 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.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

Expand All @@ -27,6 +32,9 @@ class ShareDataFragment : Fragment(), SlidePolicy {
@Inject
lateinit var backgroundWorkScheduler: BackgroundWorkScheduler

@Inject
lateinit var api: Api

private var buttonPressed: Boolean = false

override fun onCreateView(
Expand All @@ -52,6 +60,15 @@ 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
Expand Down
Loading