diff options
Diffstat (limited to 'trackers')
21 files changed, 474 insertions, 661 deletions
diff --git a/trackers/build.gradle b/trackers/build.gradle index bb9489a..737db5a 100644 --- a/trackers/build.gradle +++ b/trackers/build.gradle @@ -1,4 +1,5 @@ /* + Copyright (C) 2023 MURENA SAS Copyright (C) 2022 ECORP This program is free software; you can redistribute it and/or @@ -42,9 +43,15 @@ android { } dependencies { - implementation project(':privacymodule-api') + implementation project(':core') implementation( + libs.androidx.work.ktx, + libs.bundles.koin, libs.bundles.kotlin.android.coroutines, + libs.google.gson, + libs.retrofit, + libs.retrofit.scalars, + libs.timber ) } diff --git a/trackers/src/main/AndroidManifest.xml b/trackers/src/main/AndroidManifest.xml index debdf61..615d310 100644 --- a/trackers/src/main/AndroidManifest.xml +++ b/trackers/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- + Copyright (C) 2023 MURENA SAS Copyright (C) 2022 ECORP This program is free software: you can redistribute it and/or modify @@ -16,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="foundation.e.privacymodules.trackers"> + package="foundation.e.advancedprivacy.trackers"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> @@ -29,7 +30,7 @@ <application> <service - android:name="foundation.e.privacymodules.trackers.DNSBlockerService" + android:name="foundation.e.advancedprivacy.trackers.services.DNSBlockerService" android:enabled="true" android:exported="true" /> </application> diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt new file mode 100644 index 0000000..0cfb69c --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 MURENA SAS + * + * 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 <https://www.gnu.org/licenses/>. + */ + +package foundation.e.advancedprivacy.trackers + +import foundation.e.advancedprivacy.data.repositories.RemoteTrackersListRepository +import foundation.e.advancedprivacy.trackers.data.StatsDatabase +import foundation.e.advancedprivacy.trackers.data.TrackersRepository +import foundation.e.advancedprivacy.trackers.data.WhitelistRepository +import foundation.e.advancedprivacy.trackers.domain.usecases.DNSBlocker +import foundation.e.advancedprivacy.trackers.domain.usecases.StatisticsUseCase +import foundation.e.advancedprivacy.trackers.domain.usecases.TrackersLogger +import foundation.e.advancedprivacy.trackers.domain.usecases.UpdateTrackerListUseCase +import org.koin.android.ext.koin.androidContext +import org.koin.core.module.dsl.factoryOf +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val trackersModule = module { + + factoryOf(::RemoteTrackersListRepository) + factoryOf(::UpdateTrackerListUseCase) + + singleOf(::TrackersRepository) + single { + StatsDatabase( + context = androidContext(), + trackersRepository = get() + ) + } + + single { + StatisticsUseCase( + database = get(), + appListsRepository = get() + ) + } + + single { + WhitelistRepository( + context = androidContext(), + appListsRepository = get() + ) + } + + factory { + DNSBlocker( + context = androidContext(), + trackersLogger = get(), + trackersRepository = get(), + whitelistRepository = get() + ) + } + + factory { + TrackersLogger(statisticsUseCase = get()) + } +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/ETrackersResponse.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/ETrackersResponse.kt new file mode 100644 index 0000000..1b38ecf --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/ETrackersResponse.kt @@ -0,0 +1,10 @@ +package foundation.e.advancedprivacy.trackers.data + +data class ETrackersResponse(val trackers: List<ETracker>) { + data class ETracker( + val id: String?, + val hostnames: List<String>?, + val name: String?, + val exodusId: String? + ) +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/RemoteTrackersListRepository.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/RemoteTrackersListRepository.kt new file mode 100644 index 0000000..c2c0768 --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/RemoteTrackersListRepository.kt @@ -0,0 +1,61 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +package foundation.e.advancedprivacy.data.repositories + +import retrofit2.Retrofit +import retrofit2.converter.scalars.ScalarsConverterFactory +import retrofit2.http.GET +import timber.log.Timber +import java.io.File +import java.io.FileWriter +import java.io.IOException +import java.io.PrintWriter + +class RemoteTrackersListRepository { + + fun saveData(file: File, data: String): Boolean { + try { + val fos = FileWriter(file, false) + val ps = PrintWriter(fos) + ps.apply { + print(data) + flush() + close() + } + return true + } catch (e: IOException) { + Timber.e("While saving tracker file.", e) + } + return false + } +} + +interface ETrackersApi { + companion object { + fun build(): ETrackersApi { + val retrofit = Retrofit.Builder() + .baseUrl("https://gitlab.e.foundation/e/os/tracker-list/-/raw/main/") + .addConverterFactory(ScalarsConverterFactory.create()) + .build() + return retrofit.create(ETrackersApi::class.java) + } + } + + @GET("list/e_trackers.json") + suspend fun trackers(): String +} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt index 4d287d4..6aa76cf 100644 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package foundation.e.privacymodules.trackers.data +package foundation.e.advancedprivacy.trackers.data import android.content.ContentValues import android.content.Context @@ -25,13 +25,13 @@ import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import android.provider.BaseColumns import androidx.core.database.getStringOrNull -import foundation.e.privacymodules.trackers.api.Tracker -import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_APPID -import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED -import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED -import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TIMESTAMP -import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TRACKER -import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.TABLE_NAME +import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_APPID +import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED +import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED +import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TIMESTAMP +import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TRACKER +import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.TABLE_NAME +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker import timber.log.Timber import java.time.ZonedDateTime import java.time.format.DateTimeFormatter @@ -39,7 +39,10 @@ import java.time.temporal.ChronoUnit import java.time.temporal.TemporalUnit import java.util.concurrent.TimeUnit -class StatsDatabase(context: Context) : +class StatsDatabase( + context: Context, + private val trackersRepository: TrackersRepository +) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { companion object { @@ -84,7 +87,6 @@ class StatsDatabase(context: Context) : ) private val lock = Any() - private val trackersRepository = TrackersRepository.getInstance() override fun onCreate(db: SQLiteDatabase) { db.execSQL(SQL_CREATE_TABLE) diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/TrackersRepository.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/TrackersRepository.kt new file mode 100644 index 0000000..a7d5e49 --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/TrackersRepository.kt @@ -0,0 +1,109 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +package foundation.e.advancedprivacy.trackers.data + +import android.content.Context +import com.google.gson.Gson +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import timber.log.Timber +import java.io.File +import java.io.FileInputStream +import java.io.InputStreamReader + +class TrackersRepository( + private val context: Context, + coroutineScope: CoroutineScope +) { + + private var trackersById: Map<String, Tracker> = HashMap() + private var hostnameToId: Map<String, String> = HashMap() + + private val eTrackerFileName = "e_trackers.json" + val eTrackerFile = File(context.filesDir.absolutePath, eTrackerFileName) + + init { + coroutineScope.launch(Dispatchers.IO) { + initTrackersFile() + } + } + fun initTrackersFile() { + try { + var inputStream = context.assets.open(eTrackerFileName) + if (eTrackerFile.exists()) { + inputStream = FileInputStream(eTrackerFile) + } + val reader = InputStreamReader(inputStream, "UTF-8") + val trackerResponse = + Gson().fromJson(reader, ETrackersResponse::class.java) + + setTrackersList(mapper(trackerResponse)) + + reader.close() + inputStream.close() + } catch (e: Exception) { + Timber.e("While parsing trackers in assets", e) + } + } + + private fun mapper(response: ETrackersResponse): List<Tracker> { + return response.trackers.mapNotNull { + try { + it.toTracker() + } catch (e: Exception) { + null + } + } + } + + private fun ETrackersResponse.ETracker.toTracker(): Tracker { + return Tracker( + id = id!!, + hostnames = hostnames!!.toSet(), + label = name!!, + exodusId = exodusId + ) + } + + private fun setTrackersList(list: List<Tracker>) { + val trackersById: MutableMap<String, Tracker> = HashMap() + val hostnameToId: MutableMap<String, String> = HashMap() + list.forEach { tracker -> + trackersById[tracker.id] = tracker + for (hostname in tracker.hostnames) { + hostnameToId[hostname] = tracker.id + } + } + this.trackersById = trackersById + this.hostnameToId = hostnameToId + } + + fun isTracker(hostname: String?): Boolean { + return hostnameToId.containsKey(hostname) + } + + fun getTrackerId(hostname: String?): String? { + return hostnameToId[hostname] + } + + fun getTracker(id: String?): Tracker? { + return trackersById[id] + } +} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/WhitelistRepository.kt index 2763d06..429c5e9 100644 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/WhitelistRepository.kt @@ -16,15 +16,19 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package foundation.e.privacymodules.trackers.data +package foundation.e.advancedprivacy.trackers.data import android.content.Context import android.content.SharedPreferences -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.api.Tracker +import foundation.e.advancedprivacy.data.repositories.AppListsRepository +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker import java.io.File -class WhitelistRepository private constructor(context: Context) { +class WhitelistRepository( + context: Context, + private val appListsRepository: AppListsRepository +) { private var appsWhitelist: Set<String> = HashSet() private var appUidsWhitelist: Set<Int> = HashSet() @@ -32,7 +36,6 @@ class WhitelistRepository private constructor(context: Context) { private var trackersWhitelistByUid: Map<Int, MutableSet<String>> = HashMap() private val prefs: SharedPreferences - private var getAppByAPId: ((String) -> ApplicationDescription?)? = null companion object { private const val SHARED_PREFS_FILE = "trackers_whitelist_v2" @@ -41,30 +44,17 @@ class WhitelistRepository private constructor(context: Context) { private const val KEY_APP_TRACKERS_WHITELIST_PREFIX = "app_trackers_whitelist_" private const val SHARED_PREFS_FILE_V1 = "trackers_whitelist.prefs" - - private var instance: WhitelistRepository? = null - fun getInstance(context: Context): WhitelistRepository { - return instance ?: WhitelistRepository(context).apply { instance = this } - } } init { prefs = context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE) reloadCache() + migrate(context) } - fun setAppGetters( - context: Context, - getAppByAPId: (String) -> ApplicationDescription?, - getAppByUid: (Int) -> ApplicationDescription? - ) { - this.getAppByAPId = getAppByAPId - migrate(context, getAppByUid) - } - - private fun migrate(context: Context, getAppByUid: (Int) -> ApplicationDescription?) { + private fun migrate(context: Context) { if (context.sharedPreferencesExists(SHARED_PREFS_FILE_V1)) { - migrate1To2(context, getAppByUid) + migrate1To2(context) } } @@ -74,7 +64,7 @@ class WhitelistRepository private constructor(context: Context) { ).exists() } - private fun migrate1To2(context: Context, getAppByUid: (Int) -> ApplicationDescription?) { + private fun migrate1To2(context: Context) { val prefsV1 = context.getSharedPreferences(SHARED_PREFS_FILE_V1, Context.MODE_PRIVATE) val editorV2 = prefs.edit() @@ -83,7 +73,7 @@ class WhitelistRepository private constructor(context: Context) { val apIds = prefsV1.getStringSet(KEY_APPS_WHITELIST, HashSet())?.mapNotNull { try { val uid = it.toInt() - getAppByUid(uid)?.apId + appListsRepository.getApp(uid)?.apId } catch (e: Exception) { null } }?.toSet() ?: HashSet() @@ -93,7 +83,7 @@ class WhitelistRepository private constructor(context: Context) { if (key.startsWith(KEY_APP_TRACKERS_WHITELIST_PREFIX)) { try { val uid = key.substring(KEY_APP_TRACKERS_WHITELIST_PREFIX.length).toInt() - val apId = getAppByUid(uid)?.apId + val apId = appListsRepository.getApp(uid)?.apId apId?.let { val trackers = prefsV1.getStringSet(key, emptySet()) editorV2.putStringSet(buildAppTrackersKey(apId), trackers) @@ -117,13 +107,13 @@ class WhitelistRepository private constructor(context: Context) { private fun reloadAppsWhiteList() { appsWhitelist = prefs.getStringSet(KEY_APPS_WHITELIST, HashSet()) ?: HashSet() appUidsWhitelist = appsWhitelist - .mapNotNull { apId -> getAppByAPId?.invoke(apId)?.uid } + .mapNotNull { apId -> appListsRepository.getApp(apId)?.uid } .toSet() } private fun refreshAppUidTrackersWhiteList() { trackersWhitelistByUid = trackersWhitelistByApp.mapNotNull { (apId, value) -> - getAppByAPId?.invoke(apId)?.uid?.let { uid -> + appListsRepository.getApp(apId)?.uid?.let { uid -> uid to value } }.toMap() @@ -190,9 +180,7 @@ class WhitelistRepository private constructor(context: Context) { } fun getWhiteListedApp(): List<ApplicationDescription> { - return getAppByAPId?.let { - appsWhitelist.mapNotNull(it) - } ?: emptyList() + return appsWhitelist.mapNotNull(appListsRepository::getApp) } fun getWhiteListForApp(app: ApplicationDescription): List<String> { diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/Tracker.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/entities/Tracker.kt index 2da5b16..5c31294 100644 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/Tracker.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/entities/Tracker.kt @@ -15,7 +15,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package foundation.e.privacymodules.trackers.api +package foundation.e.advancedprivacy.trackers.domain.entities /** * Describe a tracker. diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/DNSBlocker.kt index 44793a4..fb08910 100644 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/DNSBlocker.kt @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package foundation.e.privacymodules.trackers +package foundation.e.advancedprivacy.trackers.domain.usecases import android.content.Context import android.content.pm.PackageManager @@ -24,40 +24,38 @@ import android.net.LocalServerSocket import android.system.ErrnoException import android.system.Os import android.system.OsConstants -import android.util.Log -import foundation.e.privacymodules.trackers.data.TrackersRepository -import foundation.e.privacymodules.trackers.data.WhitelistRepository +import foundation.e.advancedprivacy.core.utils.runSuspendCatching +import foundation.e.advancedprivacy.trackers.data.TrackersRepository +import foundation.e.advancedprivacy.trackers.data.WhitelistRepository +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import timber.log.Timber import java.io.BufferedReader -import java.io.IOException import java.io.InputStreamReader import java.io.PrintWriter -class DNSBlockerRunnable( +class DNSBlocker( context: Context, - private val trackersLogger: TrackersLogger, + val trackersLogger: TrackersLogger, private val trackersRepository: TrackersRepository, private val whitelistRepository: WhitelistRepository -) : Runnable { - var resolverReceiver: LocalServerSocket? = null - var stopped = false +) { + private var resolverReceiver: LocalServerSocket? = null private var eBrowserAppUid = -1 companion object { private const val SOCKET_NAME = "foundation.e.advancedprivacy" private const val E_BROWSER_DOT_SERVER = "chrome.cloudflare-dns.com" - private const val TAG = "DNSBlockerRunnable" } init { initEBrowserDoTFix(context) } - @Synchronized - fun stop() { - stopped = true - closeSocket() - } - private fun closeSocket() { // Known bug and workaround that LocalServerSocket::close is not working well // https://issuetracker.google.com/issues/36945762 @@ -68,29 +66,29 @@ class DNSBlockerRunnable( resolverReceiver = null } catch (e: ErrnoException) { if (e.errno != OsConstants.EBADF) { - Log.w(TAG, "Socket already closed") + Timber.w("Socket already closed") } else { - Log.e(TAG, "Exception: cannot close DNS port on stop $SOCKET_NAME !", e) + Timber.e(e, "Exception: cannot close DNS port on stop $SOCKET_NAME !") } } catch (e: Exception) { - Log.e(TAG, "Exception: cannot close DNS port on stop $SOCKET_NAME !", e) + Timber.e(e, "Exception: cannot close DNS port on stop $SOCKET_NAME !") } } } - override fun run() { - val resolverReceiver = try { + fun listenJob(scope: CoroutineScope): Job = scope.launch(Dispatchers.IO) { + val resolverReceiver = runSuspendCatching { LocalServerSocket(SOCKET_NAME) - } catch (eio: IOException) { - Log.e(TAG, "Exception:Cannot open DNS port $SOCKET_NAME !", eio) - return + }.getOrElse { + Timber.e(it, "Exception: cannot open DNS port on $SOCKET_NAME") + return@launch } - this.resolverReceiver = resolverReceiver - Log.d(TAG, "DNSFilterProxy running on port $SOCKET_NAME !") + this@DNSBlocker.resolverReceiver = resolverReceiver + Timber.d("DNSFilterProxy running on port $SOCKET_NAME") - while (!stopped) { - try { + while (isActive) { + runSuspendCatching { val socket = resolverReceiver.accept() val reader = BufferedReader(InputStreamReader(socket.inputStream)) val line = reader.readLine() @@ -114,9 +112,13 @@ class DNSBlockerRunnable( writer.println("pass") } socket.close() - // Printing bufferedreader data - } catch (e: IOException) { - Log.w(TAG, "Exception while listening DNS resolver", e) + }.onFailure { + if (it is CancellationException) { + closeSocket() + throw it + } else { + Timber.w(it, "Exception while listening DNS resolver") + } } } } @@ -126,7 +128,7 @@ class DNSBlockerRunnable( eBrowserAppUid = context.packageManager.getApplicationInfo("foundation.e.browser", 0).uid } catch (e: PackageManager.NameNotFoundException) { - Log.i(TAG, "no E Browser package found.") + Timber.i(e, "no E Browser package found.") } } diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt index 8f02adb..55efeb9 100644 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt @@ -16,46 +16,27 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package foundation.e.privacymodules.trackers.data - -import android.content.Context -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.api.Tracker +package foundation.e.advancedprivacy.trackers.domain.usecases + +import foundation.e.advancedprivacy.data.repositories.AppListsRepository +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.trackers.data.StatsDatabase +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow import java.time.temporal.TemporalUnit -class StatsRepository private constructor(context: Context) { - private val database: StatsDatabase - private var newDataCallback: (() -> Unit)? = null - private var getAppByUid: ((Int) -> ApplicationDescription?)? = null - private var getAppByAPId: ((String) -> ApplicationDescription?)? = null - - companion object { - private var instance: StatsRepository? = null - fun getInstance(context: Context): StatsRepository { - return instance ?: StatsRepository(context).apply { instance = this } - } - } - - fun setAppGetters( - getAppByUid: (Int) -> ApplicationDescription?, - getAppByAPId: (String) -> ApplicationDescription? - ) { - this.getAppByUid = getAppByUid - this.getAppByAPId = getAppByAPId - } - - init { - database = StatsDatabase(context) - } - - fun setNewDataCallback(callback: () -> Unit) { - newDataCallback = callback - } +class StatisticsUseCase( + private val database: StatsDatabase, + private val appListsRepository: AppListsRepository +) { + private val _newDataAvailable = MutableSharedFlow<Unit>() + val newDataAvailable: SharedFlow<Unit> = _newDataAvailable - fun logAccess(trackerId: String?, appUid: Int, blocked: Boolean) { - getAppByUid?.invoke(appUid)?.let { app -> + suspend fun logAccess(trackerId: String?, appUid: Int, blocked: Boolean) { + appListsRepository.getApp(appUid)?.let { app -> database.logAccess(trackerId, app.apId, blocked) - newDataCallback?.invoke() + _newDataAvailable.emit(Unit) } } @@ -94,12 +75,12 @@ class StatsRepository private constructor(context: Context) { } fun getMostLeakedApp(periodCount: Int, periodUnit: TemporalUnit): ApplicationDescription? { - return getAppByAPId?.invoke(database.getMostLeakedAppId(periodCount, periodUnit)) + return appListsRepository.getApp(database.getMostLeakedAppId(periodCount, periodUnit)) } private fun <K> Map<String, K>.mapByAppIdToApp(): Map<ApplicationDescription, K> { return entries.mapNotNull { (apId, value) -> - getAppByAPId?.invoke(apId)?.let { it to value } + appListsRepository.getApp(apId)?.let { it to value } }.toMap() } } diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/TrackersLogger.kt index f3c4745..411b4ab 100644 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/TrackersLogger.kt @@ -16,49 +16,40 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package foundation.e.privacymodules.trackers - -import android.content.Context -import android.util.Log -import foundation.e.privacymodules.trackers.data.StatsRepository +package foundation.e.advancedprivacy.trackers.domain.usecases + +import foundation.e.advancedprivacy.core.utils.runSuspendCatching +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import timber.log.Timber import java.util.concurrent.LinkedBlockingQueue -class TrackersLogger(context: Context) { - private val statsRepository = StatsRepository.getInstance(context) +class TrackersLogger( + private val statisticsUseCase: StatisticsUseCase, +) { private val queue = LinkedBlockingQueue<DetectedTracker>() - private var stopped = false - - companion object { - private const val TAG = "TrackerModule" - } - - init { - startWriteLogLoop() - } - - fun stop() { - stopped = true - } fun logAccess(trackerId: String?, appUid: Int, wasBlocked: Boolean) { queue.offer(DetectedTracker(trackerId, appUid, wasBlocked)) } - private fun startWriteLogLoop() { - val writeLogRunner = Runnable { - while (!stopped) { - try { + fun writeLogJob(scope: CoroutineScope): Job { + return scope.launch(Dispatchers.IO) { + while (isActive) { + runSuspendCatching { logAccess(queue.take()) - } catch (e: InterruptedException) { - Log.e(TAG, "writeLogLoop detectedTrackersQueue.take() interrupted: ", e) + }.onFailure { + Timber.e(it, "writeLogLoop detectedTrackersQueue.take() interrupted: ") } } } - Thread(writeLogRunner).start() } - fun logAccess(detectedTracker: DetectedTracker) { - statsRepository.logAccess( + private suspend fun logAccess(detectedTracker: DetectedTracker) { + statisticsUseCase.logAccess( detectedTracker.trackerId, detectedTracker.appUid, detectedTracker.wasBlocked diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/UpdateTrackerListUseCase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/UpdateTrackerListUseCase.kt new file mode 100644 index 0000000..3593dbb --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/UpdateTrackerListUseCase.kt @@ -0,0 +1,29 @@ +package foundation.e.advancedprivacy.trackers.domain.usecases + +import foundation.e.advancedprivacy.data.repositories.ETrackersApi +import foundation.e.advancedprivacy.data.repositories.RemoteTrackersListRepository +import foundation.e.advancedprivacy.trackers.data.TrackersRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import timber.log.Timber + +class UpdateTrackerListUseCase( + private val remoteTrackersListRepository: RemoteTrackersListRepository, + private val trackersRepository: TrackersRepository, + private val coroutineScope: CoroutineScope, + +) { + fun updateTrackers() = coroutineScope.launch { + update() + } + + suspend fun update() { + val api = ETrackersApi.build() + try { + remoteTrackersListRepository.saveData(trackersRepository.eTrackerFile, api.trackers()) + trackersRepository.initTrackersFile() + } catch (e: Exception) { + Timber.e("While updating trackers", e) + } + } +} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/DNSBlockerService.kt index c2ad16b..25539e1 100644 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/DNSBlockerService.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify @@ -15,28 +16,28 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package foundation.e.privacymodules.trackers +package foundation.e.advancedprivacy.trackers.services import android.app.Service import android.content.Intent import android.os.IBinder -import android.util.Log -import foundation.e.privacymodules.trackers.data.TrackersRepository -import foundation.e.privacymodules.trackers.data.WhitelistRepository +import foundation.e.advancedprivacy.trackers.domain.usecases.DNSBlocker +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import org.koin.java.KoinJavaComponent.get class DNSBlockerService : Service() { - private var trackersLogger: TrackersLogger? = null - companion object { - private const val TAG = "DNSBlockerService" - private var sDNSBlocker: DNSBlockerRunnable? = null const val ACTION_START = "foundation.e.privacymodules.trackers.intent.action.START" const val EXTRA_ENABLE_NOTIFICATION = "foundation.e.privacymodules.trackers.intent.extra.ENABLED_NOTIFICATION" } + private var coroutineScope = CoroutineScope(Dispatchers.IO) + private var dnsBlocker: DNSBlocker? = null + override fun onBind(intent: Intent): IBinder? { - // TODO: Return the communication channel to the service. throw UnsupportedOperationException("Not yet implemented") } @@ -52,28 +53,16 @@ class DNSBlockerService : Service() { } private fun start() { - try { - val trackersLogger = TrackersLogger(this) - this.trackersLogger = trackersLogger - - sDNSBlocker = DNSBlockerRunnable( - this, - trackersLogger, - TrackersRepository.getInstance(), - WhitelistRepository.getInstance(this) - ) - Thread(sDNSBlocker).start() - } catch (e: Exception) { - Log.e(TAG, "Error while starting DNSBlocker service", e) - stop() + coroutineScope = CoroutineScope(Dispatchers.IO) + get<DNSBlocker>(DNSBlocker::class.java).apply { + this@DNSBlockerService.dnsBlocker = this + trackersLogger.writeLogJob(coroutineScope) + listenJob(coroutineScope) } } private fun stop() { - sDNSBlocker?.stop() - sDNSBlocker = null - - trackersLogger?.stop() - trackersLogger = null + kotlin.runCatching { coroutineScope.cancel() } + dnsBlocker = null } } diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/ForegroundStarter.kt index 69b4f28..a0cea43 100644 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/ForegroundStarter.kt @@ -15,7 +15,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package foundation.e.privacymodules.trackers +package foundation.e.advancedprivacy.trackers.services import android.app.Notification import android.app.NotificationChannel diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/UpdateTrackersWorker.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/UpdateTrackersWorker.kt new file mode 100644 index 0000000..50aa082 --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/UpdateTrackersWorker.kt @@ -0,0 +1,60 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +package foundation.e.advancedprivacy.trackers.services + +import android.content.Context +import androidx.work.Constraints +import androidx.work.CoroutineWorker +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import foundation.e.advancedprivacy.trackers.domain.usecases.UpdateTrackerListUseCase +import org.koin.java.KoinJavaComponent.get +import java.util.concurrent.TimeUnit + +class UpdateTrackersWorker(appContext: Context, workerParams: WorkerParameters) : + CoroutineWorker(appContext, workerParams) { + + override suspend fun doWork(): Result { + val updateTrackersUsecase: UpdateTrackerListUseCase = get(UpdateTrackerListUseCase::class.java) + + updateTrackersUsecase.updateTrackers() + return Result.success() + } + + companion object { + private val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + fun periodicUpdate(context: Context) { + val request = PeriodicWorkRequestBuilder<UpdateTrackersWorker>( + 7, TimeUnit.DAYS + ) + .setConstraints(constraints).build() + + WorkManager.getInstance(context).enqueueUniquePeriodicWork( + UpdateTrackersWorker::class.qualifiedName ?: "", + ExistingPeriodicWorkPolicy.KEEP, + request + ) + } + } +} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt deleted file mode 100644 index 7463b22..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2023 MURENA SAS - * 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 <https://www.gnu.org/licenses/>. - */ - -package foundation.e.privacymodules.trackers.api - -import android.content.Context -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.data.TrackersRepository -import foundation.e.privacymodules.trackers.data.WhitelistRepository - -class BlockTrackersPrivacyModule(context: Context) : IBlockTrackersPrivacyModule { - private val mListeners = mutableListOf<IBlockTrackersPrivacyModule.Listener>() - private val trackersRepository = TrackersRepository.getInstance() - private val whitelistRepository = WhitelistRepository.getInstance(context) - - companion object { - private var instance: BlockTrackersPrivacyModule? = null - - fun getInstance(context: Context): BlockTrackersPrivacyModule { - return instance ?: BlockTrackersPrivacyModule(context).apply { instance = this } - } - } - - override fun addListener(listener: IBlockTrackersPrivacyModule.Listener) { - mListeners.add(listener) - } - - override fun clearListeners() { - mListeners.clear() - } - - override fun disableBlocking() { - whitelistRepository.isBlockingEnabled = false - mListeners.forEach { listener -> listener.onBlockingToggle(false) } - } - - override fun enableBlocking() { - whitelistRepository.isBlockingEnabled = true - mListeners.forEach { listener -> listener.onBlockingToggle(true) } - } - - override fun getWhiteList(app: ApplicationDescription): List<Tracker> { - return whitelistRepository.getWhiteListForApp(app).mapNotNull { - trackersRepository.getTracker(it) - } - } - - override fun getWhiteListedApp(): List<ApplicationDescription> { - return whitelistRepository.getWhiteListedApp() - } - - override fun isBlockingEnabled(): Boolean { - return whitelistRepository.isBlockingEnabled - } - - override fun isWhiteListEmpty(): Boolean { - return whitelistRepository.areWhiteListEmpty() - } - - override fun isWhitelisted(app: ApplicationDescription): Boolean { - return whitelistRepository.isAppWhiteListed(app) - } - - override fun removeListener(listener: IBlockTrackersPrivacyModule.Listener) { - mListeners.remove(listener) - } - - override fun setWhiteListed( - tracker: Tracker, - app: ApplicationDescription, - isWhiteListed: Boolean - ) { - whitelistRepository.setWhiteListed(tracker, app.apId, isWhiteListed) - } - - override fun setWhiteListed(app: ApplicationDescription, isWhiteListed: Boolean) { - whitelistRepository.setWhiteListed(app.apId, isWhiteListed) - } - - override fun clearWhiteList(app: ApplicationDescription) { - whitelistRepository.clearWhiteList(app.apId) - } -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt deleted file mode 100644 index 3547b8e..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2023 MURENA SAS - * 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 <https://www.gnu.org/licenses/>. - */ - -package foundation.e.privacymodules.trackers.api - -import foundation.e.privacymodules.permissions.data.ApplicationDescription - -/** - * Manage trackers blocking and whitelisting. - */ -interface IBlockTrackersPrivacyModule { - - /** - * Get the state of the blockin module - * @return true when blocking is enabled, false otherwise. - */ - fun isBlockingEnabled(): Boolean - - /** - * Enable blocking, using the previously configured whitelists - */ - fun enableBlocking() - - /** - * Disable blocking - */ - fun disableBlocking() - - /** - * Set or unset in whitelist the App with the specified uid. - * @param app the ApplicationDescription of the app - * @param isWhiteListed true, the app will appears in whitelist, false, it won't - */ - fun setWhiteListed(app: ApplicationDescription, isWhiteListed: Boolean) - - /** - * Set or unset in whitelist the specifid tracked, for the App specified by its uid. - * @param tracker the tracker - * @param app the ApplicationDescription of the app - * @param isWhiteListed true, the app will appears in whitelist, false, it won't - */ - fun setWhiteListed(tracker: Tracker, app: ApplicationDescription, isWhiteListed: Boolean) - - /** - * Return true if nothing has been added to the whitelist : everything is blocked. - */ - fun isWhiteListEmpty(): Boolean - - /** - * Return the white listed App, by their UID - */ - fun getWhiteListedApp(): List<ApplicationDescription> - - /** - * Return true if the App is whitelisted for trackers blocking. - */ - fun isWhitelisted(app: ApplicationDescription): Boolean - - /** - * List the white listed trackers for an App specified by it uid - */ - fun getWhiteList(app: ApplicationDescription): List<Tracker> - - fun clearWhiteList(app: ApplicationDescription) - - /** - * Callback interface to get updates about the state of the Block trackers module. - */ - interface Listener { - - /** - * Called when the trackers blocking is activated or deactivated. - * @param isBlocking true when activated, false otherwise. - */ - fun onBlockingToggle(isBlocking: Boolean) - } - - fun addListener(listener: Listener) - - fun removeListener(listener: Listener) - - fun clearListeners() -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt deleted file mode 100644 index 8aaed4a..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2023 MURENA SAS - * 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 <https://www.gnu.org/licenses/>. - */ - -package foundation.e.privacymodules.trackers.api - -import foundation.e.privacymodules.permissions.data.ApplicationDescription - -/** - * Get reporting about trackers calls. - */ -interface ITrackTrackersPrivacyModule { - - fun start( - trackers: List<Tracker>, - getAppByUid: (Int) -> ApplicationDescription?, - getAppByAPId: (String) -> ApplicationDescription?, - enableNotification: Boolean = true - ) - - /** - * List all the trackers encountered for a specific app. - */ - fun getTrackersForApp(app: ApplicationDescription): List<Tracker> - - /** - * List all the trackers encountere trackers since "ever", for the given [appUids], - * or all apps if [appUids] is null - */ - fun getTrackers(apps: List<ApplicationDescription>? = null): List<Tracker> - - /** - * Return the number of encountered trackers since "ever", for the given [appUids], - * or all apps if [appUids] is null - */ - fun getTrackersCount(): Int - - /** - * Return the number of encountere trackers since "ever", for each app uid. - */ - fun getTrackersCountByApp(): Map<ApplicationDescription, Int> - - /** - * Return the number of encountered trackers for the last 24 hours - */ - fun getPastDayTrackersCount(): Int - - /** - * Return the number of encountered trackers for the last month - */ - fun getPastMonthTrackersCount(): Int - - /** - * Return the number of encountered trackers for the last year - */ - fun getPastYearTrackersCount(): Int - - /** - * Return number of trackers calls by hours, for the last 24hours. - * @return list of 24 numbers of trackers calls by hours - */ - fun getPastDayTrackersCalls(): List<Pair<Int, Int>> - - /** - * Return number of trackers calls by day, for the last 30 days. - * @return list of 30 numbers of trackers calls by day - */ - fun getPastMonthTrackersCalls(): List<Pair<Int, Int>> - - /** - * Return number of trackers calls by month, for the last 12 month. - * @return list of 12 numbers of trackers calls by month - */ - fun getPastYearTrackersCalls(): List<Pair<Int, Int>> - - fun getPastDayTrackersCallsByApps(): Map<ApplicationDescription, Pair<Int, Int>> - - fun getPastDayTrackersCallsForApp(app: ApplicationDescription): Pair<Int, Int> - - fun getPastDayMostLeakedApp(): ApplicationDescription? - - interface Listener { - - /** - * Called when a new tracker attempt is logged. Consumer may choose to call other methods - * to refresh the data. - */ - fun onNewData() - } - - fun addListener(listener: Listener) - - fun removeListener(listener: Listener) - - fun clearListeners() -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt deleted file mode 100644 index 5fc5b6b..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2023 MURENA SAS - * Copyright (C) 2021 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 <https://www.gnu.org/licenses/>. - */ - -package foundation.e.privacymodules.trackers.api - -import android.content.Context -import android.content.Intent -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.DNSBlockerService -import foundation.e.privacymodules.trackers.data.StatsRepository -import foundation.e.privacymodules.trackers.data.TrackersRepository -import foundation.e.privacymodules.trackers.data.WhitelistRepository -import java.time.temporal.ChronoUnit - -class TrackTrackersPrivacyModule(private val context: Context) : ITrackTrackersPrivacyModule { - private val statsRepository = StatsRepository.getInstance(context) - private val listeners: MutableList<ITrackTrackersPrivacyModule.Listener> = mutableListOf() - - companion object { - private var instance: TrackTrackersPrivacyModule? = null - - fun getInstance(context: Context): TrackTrackersPrivacyModule { - return instance ?: TrackTrackersPrivacyModule(context).apply { instance = this } - } - } - - init { - statsRepository.setNewDataCallback { - listeners.forEach { listener -> listener.onNewData() } - } - } - - override fun start( - trackers: List<Tracker>, - getAppByUid: (Int) -> ApplicationDescription?, - getAppByAPId: (String) -> ApplicationDescription?, - enableNotification: Boolean - ) { - TrackersRepository.getInstance().setTrackersList(trackers) - StatsRepository.getInstance(context).setAppGetters(getAppByUid, getAppByAPId) - WhitelistRepository.getInstance(context).setAppGetters(context, getAppByAPId, getAppByUid) - val intent = Intent(context, DNSBlockerService::class.java) - intent.action = DNSBlockerService.ACTION_START - intent.putExtra(DNSBlockerService.EXTRA_ENABLE_NOTIFICATION, enableNotification) - context.startService(intent) - } - - override fun getPastDayTrackersCalls(): List<Pair<Int, Int>> { - return statsRepository.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS) - } - - override fun getPastMonthTrackersCalls(): List<Pair<Int, Int>> { - return statsRepository.getTrackersCallsOnPeriod(30, ChronoUnit.DAYS) - } - - override fun getPastYearTrackersCalls(): List<Pair<Int, Int>> { - return statsRepository.getTrackersCallsOnPeriod(12, ChronoUnit.MONTHS) - } - - override fun getTrackersCount(): Int { - return statsRepository.getContactedTrackersCount() - } - - override fun getTrackersCountByApp(): Map<ApplicationDescription, Int> { - return statsRepository.getContactedTrackersCountByApp() - } - - override fun getTrackersForApp(app: ApplicationDescription): List<Tracker> { - return statsRepository.getTrackers(listOf(app)) - } - - override fun getTrackers(apps: List<ApplicationDescription>?): List<Tracker> { - return statsRepository.getTrackers(apps) - } - - override fun getPastDayTrackersCount(): Int { - return statsRepository.getActiveTrackersByPeriod(24, ChronoUnit.HOURS) - } - - override fun getPastMonthTrackersCount(): Int { - return statsRepository.getActiveTrackersByPeriod(30, ChronoUnit.DAYS) - } - - override fun getPastYearTrackersCount(): Int { - return statsRepository.getActiveTrackersByPeriod(12, ChronoUnit.MONTHS) - } - - override fun getPastDayMostLeakedApp(): ApplicationDescription? { - return statsRepository.getMostLeakedApp(24, ChronoUnit.HOURS) - } - - override fun getPastDayTrackersCallsByApps(): Map<ApplicationDescription, Pair<Int, Int>> { - return statsRepository.getCallsByApps(24, ChronoUnit.HOURS) - } - - override fun getPastDayTrackersCallsForApp(app: ApplicationDescription): Pair<Int, Int> { - return statsRepository.getCalls(app, 24, ChronoUnit.HOURS) - } - - override fun addListener(listener: ITrackTrackersPrivacyModule.Listener) { - listeners.add(listener) - } - - override fun removeListener(listener: ITrackTrackersPrivacyModule.Listener) { - listeners.remove(listener) - } - - override fun clearListeners() { - listeners.clear() - } -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.kt deleted file mode 100644 index 994bccf..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 <https://www.gnu.org/licenses/>. - */ - -package foundation.e.privacymodules.trackers.data - -import foundation.e.privacymodules.trackers.api.Tracker - -class TrackersRepository private constructor() { - private var trackersById: Map<String, Tracker> = HashMap() - private var hostnameToId: Map<String, String> = HashMap() - - companion object { - private var instance: TrackersRepository? = null - fun getInstance(): TrackersRepository { - return instance ?: TrackersRepository().apply { instance = this } - } - } - - fun setTrackersList(list: List<Tracker>) { - val trackersById: MutableMap<String, Tracker> = HashMap() - val hostnameToId: MutableMap<String, String> = HashMap() - list.forEach { tracker -> - trackersById[tracker.id] = tracker - for (hostname in tracker.hostnames) { - hostnameToId[hostname] = tracker.id - } - } - this.trackersById = trackersById - this.hostnameToId = hostnameToId - } - - fun isTracker(hostname: String?): Boolean { - return hostnameToId.containsKey(hostname) - } - - fun getTrackerId(hostname: String?): String? { - return hostnameToId[hostname] - } - - fun getTracker(id: String?): Tracker? { - return trackersById[id] - } -} |