Self-hosted, federated location sharing app and server that prioritizes user privacy and security
end-to-end-encryption location-sharing privacy self-hosted federated

Fetch device location in LocationWorker using FusedLocationProvider

azom.dev 48cebc6e aba825cf

verified
+54 -3
+1
app/src-tauri/gen/android/app/build.gradle.kts
··· 63 63 implementation("androidx.activity:activity-ktx:1.10.1") 64 64 implementation("com.google.android.material:material:1.12.0") 65 65 implementation("androidx.work:work-runtime-ktx:2.10.1") 66 + implementation("com.google.android.gms:play-services-location:21.3.0") 66 67 testImplementation("junit:junit:4.13.2") 67 68 androidTestImplementation("androidx.test.ext:junit:1.1.4") 68 69 androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
+53 -3
app/src-tauri/gen/android/app/src/main/java/dev/azom/privacypin/LocationWorker.kt
··· 1 1 package dev.azom.privacypin 2 2 3 + import android.Manifest 3 4 import android.content.Context 5 + import android.content.pm.PackageManager 4 6 import android.util.Log 7 + import androidx.core.content.ContextCompat 5 8 import androidx.work.CoroutineWorker 6 9 import androidx.work.WorkerParameters 10 + import com.google.android.gms.location.LocationServices 11 + import com.google.android.gms.location.Priority 12 + import com.google.android.gms.tasks.CancellationTokenSource 13 + import kotlinx.coroutines.suspendCancellableCoroutine 7 14 import java.time.Instant 8 15 import java.time.ZoneId 9 16 import java.time.format.DateTimeFormatter 17 + import kotlin.coroutines.resume 18 + import kotlin.coroutines.resumeWithException 10 19 11 20 class LocationWorker( 12 21 appContext: Context, ··· 25 34 26 35 Log.i(TAG, "LocationWorker executed at $now (attempt #$runAttemptCount)") 27 36 28 - // TODO Step 3: Read Tauri store (settings.json) 29 - // TODO Step 4: Fetch device location 30 - // TODO Step 5: Send location to server 37 + val hasCoarse = ContextCompat.checkSelfPermission( 38 + applicationContext, Manifest.permission.ACCESS_COARSE_LOCATION 39 + ) == PackageManager.PERMISSION_GRANTED 40 + 41 + val hasFine = ContextCompat.checkSelfPermission( 42 + applicationContext, Manifest.permission.ACCESS_FINE_LOCATION 43 + ) == PackageManager.PERMISSION_GRANTED 44 + 45 + if (!hasCoarse && !hasFine) { 46 + Log.e(TAG, "Location permissions not granted, skipping") 47 + return Result.failure() 48 + } 49 + 50 + try { 51 + val locationClient = LocationServices.getFusedLocationProviderClient(applicationContext) 52 + val cancellationSource = CancellationTokenSource() 53 + 54 + val priority = if (hasFine) { 55 + Priority.PRIORITY_HIGH_ACCURACY 56 + } else { 57 + Priority.PRIORITY_BALANCED_POWER_ACCURACY 58 + } 59 + 60 + val location = suspendCancellableCoroutine { cont -> 61 + locationClient.getCurrentLocation(priority, cancellationSource.token) 62 + .addOnSuccessListener { loc -> cont.resume(loc) } 63 + .addOnFailureListener { e -> cont.resumeWithException(e) } 64 + .addOnCanceledListener { cont.cancel() } 65 + 66 + cont.invokeOnCancellation { cancellationSource.cancel() } 67 + } 68 + 69 + if (location != null) { 70 + Log.i(TAG, "Location: ${location.latitude},${location.longitude} (accuracy: ${location.accuracy}m)") 71 + } else { 72 + Log.w(TAG, "Location was null (device location might be disabled)") 73 + } 74 + } catch (e: SecurityException) { 75 + Log.e(TAG, "SecurityException while fetching location: ${e.message}") 76 + return Result.failure() 77 + } catch (e: Exception) { 78 + Log.e(TAG, "Failed to fetch location: ${e.message}") 79 + return Result.retry() 80 + } 31 81 32 82 Log.i(TAG, "LocationWorker finished successfully") 33 83 return Result.success()