From 53f4a9ce311d612d43fa770cf7e8f8e98fbb43a0 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Tue, 12 Sep 2023 06:17:39 +0000 Subject: 2: organise module with clean archi, use Koin for injection. --- .../e/advancedprivacy/fakelocation/KoinModule.kt | 9 ++ .../domain/usecases/FakeLocationModule.kt | 133 +++++++++++++++++++++ .../fakelocation/services/FakeLocationService.kt | 111 +++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/KoinModule.kt create mode 100644 fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/domain/usecases/FakeLocationModule.kt create mode 100644 fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/services/FakeLocationService.kt (limited to 'fakelocation/src/main/java/foundation/e/advancedprivacy') diff --git a/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/KoinModule.kt b/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/KoinModule.kt new file mode 100644 index 0000000..b833181 --- /dev/null +++ b/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/KoinModule.kt @@ -0,0 +1,9 @@ +package foundation.e.advancedprivacy.fakelocation + +import foundation.e.advancedprivacy.fakelocation.domain.usecases.FakeLocationModule +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val fakelocationModule = module { + singleOf(::FakeLocationModule) +} diff --git a/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/domain/usecases/FakeLocationModule.kt b/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/domain/usecases/FakeLocationModule.kt new file mode 100644 index 0000000..c9aac0a --- /dev/null +++ b/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/domain/usecases/FakeLocationModule.kt @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.advancedprivacy.fakelocation.domain.usecases + +import android.content.Context +import android.content.Context.LOCATION_SERVICE +import android.location.Location +import android.location.LocationManager +import android.location.LocationManager.GPS_PROVIDER +import android.location.LocationManager.NETWORK_PROVIDER +import android.location.provider.ProviderProperties +import android.os.Build +import android.os.SystemClock +import android.util.Log +import foundation.e.advancedprivacy.fakelocation.services.FakeLocationService + +/** + * Implementation of the functionality of fake location. + * All of them are available for normal application, so just one version is enough. + * + * @param context an Android context, to retrieve system services for example. + */ +class FakeLocationModule(private val context: Context) { + companion object { + private const val TAG = "FakeLocationModule" + } + + /** + * Handy accessor to the locationManager service. + * We avoid getting it on module initialization to wait for the context to be ready. + */ + private val locationManager: LocationManager get() = + context.getSystemService(LOCATION_SERVICE) as LocationManager + + /** + * List of all the Location provider that will be mocked. + */ + private val providers = locationManager.allProviders + .intersect(listOf(GPS_PROVIDER, NETWORK_PROVIDER)) + + /** + * @see IFakeLocationModule.startFakeLocation + */ + @Synchronized + fun startFakeLocation() { + providers.forEach { provider -> + try { + locationManager.removeTestProvider(provider) + } catch (e: Exception) { + Log.w(TAG, "Test provider $provider already removed.") + } + + locationManager.addTestProvider( + provider, + false, + false, + false, + false, + false, + true, + true, + ProviderProperties.POWER_USAGE_LOW, + ProviderProperties.ACCURACY_FINE + ) + try { + locationManager.setTestProviderEnabled(provider, true) + } catch (e: Exception) { + Log.e(TAG, "Can't enable test $provider", e) + } + } + } + + fun setFakeLocation(latitude: Double, longitude: Double) { + context.startService(FakeLocationService.buildFakeLocationIntent(context, latitude, longitude)) + } + + internal fun setTestProviderLocation(latitude: Double, longitude: Double) { + providers.forEach { provider -> + val location = Location(provider) + location.latitude = latitude + location.longitude = longitude + + // Set default value for all the other required fields. + location.altitude = 3.0 + location.time = System.currentTimeMillis() + location.speed = 0.01f + location.bearing = 1f + location.accuracy = 3f + location.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + location.bearingAccuracyDegrees = 0.1f + location.verticalAccuracyMeters = 0.1f + location.speedAccuracyMetersPerSecond = 0.01f + } + try { + locationManager.setTestProviderLocation(provider, location) + } catch (e: Exception) { + Log.e(TAG, "Can't set location for test provider $provider", e) + } + } + } + + /** + * @see IFakeLocationModule.stopFakeLocation + */ + fun stopFakeLocation() { + context.stopService(FakeLocationService.buildStopIntent(context)) + providers.forEach { provider -> + try { + locationManager.setTestProviderEnabled(provider, false) + locationManager.removeTestProvider(provider) + } catch (e: Exception) { + Log.d(TAG, "Test provider $provider already removed.") + } + } + } +} diff --git a/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/services/FakeLocationService.kt b/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/services/FakeLocationService.kt new file mode 100644 index 0000000..6eaae54 --- /dev/null +++ b/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/services/FakeLocationService.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.advancedprivacy.fakelocation.services + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.CountDownTimer +import android.os.IBinder +import android.util.Log +import foundation.e.advancedprivacy.fakelocation.domain.usecases.FakeLocationModule + +class FakeLocationService : Service() { + + enum class Actions { + START_FAKE_LOCATION + } + + companion object { + private const val PERIOD_LOCATION_UPDATE = 1000L + private const val PERIOD_UPDATES_SERIE = 2 * 60 * 1000L + + private const val PARAM_LATITUDE = "PARAM_LATITUDE" + private const val PARAM_LONGITUDE = "PARAM_LONGITUDE" + + fun buildFakeLocationIntent(context: Context, latitude: Double, longitude: Double): Intent { + return Intent(context, FakeLocationService::class.java).apply { + action = Actions.START_FAKE_LOCATION.name + putExtra(PARAM_LATITUDE, latitude) + putExtra(PARAM_LONGITUDE, longitude) + } + } + + fun buildStopIntent(context: Context) = Intent(context, FakeLocationService::class.java) + } + + private lateinit var fakeLocationModule: FakeLocationModule + + private var countDownTimer: CountDownTimer? = null + + private var fakeLocation: Pair? = null + + override fun onCreate() { + super.onCreate() + fakeLocationModule = FakeLocationModule(applicationContext) + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + intent?.let { + when (it.action?.let { str -> Actions.valueOf(str) }) { + Actions.START_FAKE_LOCATION -> { + + fakeLocation = Pair( + it.getDoubleExtra(PARAM_LATITUDE, 0.0), + it.getDoubleExtra(PARAM_LONGITUDE, 0.0) + ) + initTimer() + } + else -> {} + } + } + + return START_STICKY + } + + override fun onDestroy() { + countDownTimer?.cancel() + super.onDestroy() + } + + private fun initTimer() { + countDownTimer?.cancel() + countDownTimer = object : CountDownTimer(PERIOD_UPDATES_SERIE, PERIOD_LOCATION_UPDATE) { + override fun onTick(millisUntilFinished: Long) { + fakeLocation?.let { + try { + fakeLocationModule.setTestProviderLocation( + it.first, + it.second + ) + } catch (e: Exception) { + Log.d("FakeLocationService", "setting fake location", e) + } + } + } + + override fun onFinish() { + initTimer() + } + }.start() + } + + override fun onBind(intent: Intent?): IBinder? { + return null + } +} -- cgit v1.2.1