-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7f4cb416c083d265558da75d457237d671..0000000000000000000000000000000000000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index ef54d456a756585cc6207eddb535458a3c8738be..216b81afe368aaa1b3fc2a1a349cd230cc434721 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -97,6 +97,7 @@ android {
persistent: "false",
mainActivityIntentFilterCategory: "android.intent.category.LAUNCHER"
]
+
signingConfig signingConfigs.debug
}
}
@@ -152,6 +153,8 @@ dependencies {
implementation project(':trackers')
implementation project(':ipscrambling')
+ eImplementation project(':trackersservicee')
+ standaloneImplementation project(':trackersservicestandalone')
implementation (
libs.e.elib,
diff --git a/app/src/main/java/foundation/e/advancedprivacy/AdvancedPrivacyApplication.kt b/app/src/main/java/foundation/e/advancedprivacy/AdvancedPrivacyApplication.kt
index 0af2a0e9e96921a511e9473a37db38c92bbb92fc..71fef00595991b21a3ffc634f8204ad7b09b5fd8 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/AdvancedPrivacyApplication.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/AdvancedPrivacyApplication.kt
@@ -18,7 +18,6 @@
package foundation.e.advancedprivacy
import android.app.Application
-import android.content.Intent
import foundation.e.advancedprivacy.common.WarningDialog
import foundation.e.advancedprivacy.domain.usecases.FakeLocationStateUseCase
import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase
@@ -27,7 +26,6 @@ import foundation.e.advancedprivacy.domain.usecases.ShowFeaturesWarningUseCase
import foundation.e.advancedprivacy.domain.usecases.TrackersStateUseCase
import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase
import foundation.e.advancedprivacy.externalinterfaces.permissions.IPermissionsPrivacyModule
-import foundation.e.advancedprivacy.trackers.services.DNSBlockerService
import foundation.e.advancedprivacy.trackers.services.UpdateTrackersWorker
import foundation.e.lib.telemetry.Telemetry
import kotlinx.coroutines.CoroutineScope
@@ -70,12 +68,7 @@ class AdvancedPrivacyApplication : Application() {
)
get(IpScramblingStateUseCase::class.java)
- get(FakeLocationStateUseCase::class.java)
get(TrackersStateUseCase::class.java)
-
- val intent = Intent(this, DNSBlockerService::class.java)
- intent.action = DNSBlockerService.ACTION_START
- intent.putExtra(DNSBlockerService.EXTRA_ENABLE_NOTIFICATION, false)
- startService(intent)
+ get(FakeLocationStateUseCase::class.java)
}
}
diff --git a/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt b/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt b/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt
index 3fbb6360b6d0769a2ad2891137de1a6f3ab6edb8..20cefd50e026f1038b3e832d2e6b9fb2a3296545 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt
@@ -22,6 +22,8 @@ import android.os.Process
import foundation.e.advancedprivacy.core.coreModule
import foundation.e.advancedprivacy.data.repositories.LocalStateRepository
import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
+import foundation.e.advancedprivacy.domain.entities.CHANNEL_TRACKER_FLAG
+import foundation.e.advancedprivacy.domain.entities.NotificationContent
import foundation.e.advancedprivacy.domain.entities.ProfileType
import foundation.e.advancedprivacy.domain.usecases.AppListUseCase
import foundation.e.advancedprivacy.domain.usecases.FakeLocationStateUseCase
@@ -39,7 +41,10 @@ import foundation.e.advancedprivacy.features.location.FakeLocationViewModel
import foundation.e.advancedprivacy.features.trackers.TrackersViewModel
import foundation.e.advancedprivacy.features.trackers.apptrackers.AppTrackersViewModel
import foundation.e.advancedprivacy.ipscrambler.ipScramblerModule
-import foundation.e.advancedprivacy.permissions.externalinterfaces.PermissionsPrivacyModule
+import foundation.e.advancedprivacy.permissions.externalinterfaces.PermissionsPrivacyModuleImpl
+import foundation.e.advancedprivacy.trackers.domain.externalinterfaces.TrackersServiceSupervisor
+import foundation.e.advancedprivacy.trackers.service.TrackersServiceSupervisorImpl
+import foundation.e.advancedprivacy.trackers.service.trackerServiceModule
import foundation.e.advancedprivacy.trackers.trackersModule
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
@@ -49,7 +54,7 @@ import org.koin.core.qualifier.named
import org.koin.dsl.module
val appModule = module {
- includes(coreModule, trackersModule, fakelocationModule, ipScramblerModule)
+ includes(coreModule, trackersModule, fakelocationModule, ipScramblerModule, trackerServiceModule)
factory { androidContext().resources }
single {
@@ -89,6 +94,16 @@ val appModule = module {
)
}
+ single(named("notificationTrackerFlag")) {
+ NotificationContent(
+ channelId = CHANNEL_TRACKER_FLAG,
+ icon = R.drawable.ic_e_app_logo,
+ title = R.string.notifications_tracker_title,
+ description = R.string.notifications_tracker_content,
+ pendingIntent = null
+ )
+ }
+
single { CityDataSource }
singleOf(::AppListUseCase)
@@ -120,7 +135,13 @@ val appModule = module {
singleOf(::TrackersStatisticsUseCase)
single {
- PermissionsPrivacyModule(context = androidContext())
+ PermissionsPrivacyModuleImpl(context = androidContext())
+ }
+
+ single {
+ TrackersServiceSupervisorImpl(
+ context = androidContext(),
+ )
}
viewModel { parameters ->
diff --git a/app/src/main/java/foundation/e/advancedprivacy/Notifications.kt b/app/src/main/java/foundation/e/advancedprivacy/Notifications.kt
index cd85e9a6dc628a9d029d17a2a0b71315c3a1d9d6..455b1a783a237f6646b682f57abd5347867cc8e9 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/Notifications.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/Notifications.kt
@@ -20,13 +20,21 @@ package foundation.e.advancedprivacy
import android.app.NotificationChannel
import android.app.NotificationManager
-import android.app.PendingIntent
import android.content.Context
import androidx.annotation.StringRes
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
+import foundation.e.advancedprivacy.core.utils.notificationBuilder
+import foundation.e.advancedprivacy.domain.entities.CHANNEL_FAKE_LOCATION_FLAG
+import foundation.e.advancedprivacy.domain.entities.CHANNEL_FIRST_BOOT
+import foundation.e.advancedprivacy.domain.entities.CHANNEL_IPSCRAMBLING_FLAG
+import foundation.e.advancedprivacy.domain.entities.CHANNEL_TRACKER_FLAG
import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode
import foundation.e.advancedprivacy.domain.entities.MainFeatures
+import foundation.e.advancedprivacy.domain.entities.NOTIFICATION_FAKE_LOCATION_FLAG
+import foundation.e.advancedprivacy.domain.entities.NOTIFICATION_FIRST_BOOT
+import foundation.e.advancedprivacy.domain.entities.NOTIFICATION_IPSCRAMBLING_FLAG
+import foundation.e.advancedprivacy.domain.entities.NotificationContent
import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase
import foundation.e.advancedprivacy.externalinterfaces.permissions.IPermissionsPrivacyModule
import foundation.e.advancedprivacy.main.MainActivity
@@ -37,14 +45,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
object Notifications {
- const val CHANNEL_FIRST_BOOT = "first_boot_notification"
- const val CHANNEL_FAKE_LOCATION_FLAG = "fake_location_flag"
- const val CHANNEL_IPSCRAMBLING_FLAG = "ipscrambling_flag"
-
- const val NOTIFICATION_FIRST_BOOT = 1000
- const val NOTIFICATION_FAKE_LOCATION_FLAG = NOTIFICATION_FIRST_BOOT + 1
- const val NOTIFICATION_IPSCRAMBLING_FLAG = NOTIFICATION_FAKE_LOCATION_FLAG + 1
-
fun showFirstBootNotification(context: Context) {
createNotificationFirstBootChannel(context)
val notificationBuilder: NotificationCompat.Builder = notificationBuilder(
@@ -88,6 +88,14 @@ object Notifications {
channelDescription = R.string.notifications_ipscrambling_channel_description
)
+ createNotificationFlagChannel(
+ context = appContext,
+ permissionsPrivacyModule = permissionsPrivacyModule,
+ channelId = CHANNEL_TRACKER_FLAG,
+ channelName = R.string.notifications_tracker_channel_name,
+ channelDescription = R.string.notifications_ipscrambling_channel_description
+ )
+
getQuickPrivacyStateUseCase.isLocationHidden.onEach {
if (it) {
showFlagNotification(appContext, MainFeatures.FAKE_LOCATION)
@@ -183,26 +191,4 @@ object Notifications {
}
NotificationManagerCompat.from(context).cancel(id)
}
-
- private data class NotificationContent(
- val channelId: String,
- val icon: Int,
- val title: Int,
- val description: Int,
- val pendingIntent: PendingIntent?
- )
-
- private fun notificationBuilder(
- context: Context,
- content: NotificationContent
- ): NotificationCompat.Builder {
- val builder = NotificationCompat.Builder(context, content.channelId)
- .setSmallIcon(content.icon)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .setContentTitle(context.getString(content.title))
- .setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(content.description)))
- content.pendingIntent?.let { builder.setContentIntent(it) }
-
- return builder
- }
}
diff --git a/app/src/main/java/foundation/e/advancedprivacy/common/BuildFlavor.kt b/app/src/main/java/foundation/e/advancedprivacy/common/BuildFlavor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ecb24cede8676c0990f6d55d5cb800920a3fed2c
--- /dev/null
+++ b/app/src/main/java/foundation/e/advancedprivacy/common/BuildFlavor.kt
@@ -0,0 +1,22 @@
+/*
+ * 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 .
+ */
+
+package foundation.e.advancedprivacy.common
+
+import foundation.e.advancedprivacy.BuildConfig
+
+const val isStandaloneBuild: Boolean = BuildConfig.FLAVOR == "standalone"
diff --git a/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt b/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt
index 80fc76016cf0471862a561c632f8d966f6382388..589aa74f4df013ef8bb0b75f3753f078526ba1d4 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt
@@ -75,7 +75,7 @@ class WarningDialog : AppCompatActivity() {
val feature = try {
intent.getParcelableExtra(PARAM_FEATURE)!!
} catch (e: Exception) {
- Timber.e("Missing mandatory activity parameter", e)
+ Timber.e(e, "Missing mandatory activity parameter")
finish()
return
}
diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt
index 30c8e6ba393b229e67ab0262c8781389937193bc..983ba714e3cd9b500b7ef973211744367af68b36 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt
@@ -202,7 +202,7 @@ class FakeLocationStateUseCase(
lastKnownLocation?.let { localListener.onLocationChanged(it) }
} catch (se: SecurityException) {
- Timber.e("Missing permission", se)
+ Timber.e(se, "Missing permission")
}
}
}
diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt
index 27e7fe40236ff03b12bca1ac59aeec77f313d8c4..9c893290d24e1f7a41e00874c06af8130a7d4b08 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt
@@ -19,6 +19,7 @@
package foundation.e.advancedprivacy.domain.usecases
import android.content.Intent
+import foundation.e.advancedprivacy.common.isStandaloneBuild
import foundation.e.advancedprivacy.data.repositories.AppListsRepository
import foundation.e.advancedprivacy.data.repositories.LocalStateRepository
import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
@@ -190,7 +191,7 @@ class IpScramblingStateUseCase(
fun startIpScrambling() {
localStateRepository.internetPrivacyMode.value = HIDE_IP_LOADING
- ipScramblerModule.start(enableNotification = false) // change the false ?
+ ipScramblerModule.start(enableNotification = isStandaloneBuild)
}
private fun map(status: IpScramblerModule.Status): InternetPrivacyMode {
diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStateUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStateUseCase.kt
index ed15a4148e8c53905d80438a60a77834afe5d48f..9b79dcc121478e71f9bbcca25458fd79e1c89002 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStateUseCase.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStateUseCase.kt
@@ -22,6 +22,7 @@ import foundation.e.advancedprivacy.data.repositories.LocalStateRepository
import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
import foundation.e.advancedprivacy.trackers.data.WhitelistRepository
import foundation.e.advancedprivacy.trackers.domain.entities.Tracker
+import foundation.e.advancedprivacy.trackers.domain.externalinterfaces.TrackersServiceSupervisor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -29,7 +30,8 @@ class TrackersStateUseCase(
private val whitelistRepository: WhitelistRepository,
private val localStateRepository: LocalStateRepository,
private val appListsRepository: AppListsRepository,
- coroutineScope: CoroutineScope
+ private val trackersServiceSupervisor: TrackersServiceSupervisor,
+ coroutineScope: CoroutineScope,
) {
init {
coroutineScope.launch {
@@ -38,6 +40,8 @@ class TrackersStateUseCase(
updateAllTrackersBlockedState()
}
}
+
+ trackersServiceSupervisor.start()
}
private fun updateAllTrackersBlockedState() {
diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt
index b0c9f39114c638101eef35a10ac2fd8646a36b9b..3d6ade0f212e876d53e8ce229cb85727afa93286 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt
@@ -24,6 +24,7 @@ import foundation.e.advancedprivacy.data.repositories.AppListsRepository
import foundation.e.advancedprivacy.domain.entities.AppWithCounts
import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
import foundation.e.advancedprivacy.domain.entities.TrackersPeriodicStatistics
+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.entities.Tracker
@@ -44,6 +45,7 @@ class TrackersStatisticsUseCase(
private val whitelistRepository: WhitelistRepository,
private val trackersRepository: TrackersRepository,
private val appListsRepository: AppListsRepository,
+ private val statsDatabase: StatsDatabase,
private val resources: Resources
) {
fun initAppList() {
@@ -52,7 +54,7 @@ class TrackersStatisticsUseCase(
@OptIn(FlowPreview::class)
fun listenUpdates(debounce: Duration = 1.seconds) =
- statisticsUseCase.newDataAvailable
+ statsDatabase.newDataAvailable
.throttleFirst(windowDuration = debounce)
.onStart { emit(Unit) }
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fac1f753f5b765a76d0bdf816f58cd1ba281db56..ba3ba03e79daf32a26957323fb145442d0241355 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -144,4 +144,9 @@
Real IP hiddenThis could impact the functioning of some applications.
+ Tracker control flag
+ Highlight that the trackers are actually logged and blocked by Advanced Privacy
+ Tracker control is on
+ This could impact the functioning of some applications.
+
diff --git a/build.gradle b/build.gradle
index a57ee217038642a3a9864c45c99cdad110c9a9ae..cd192ee3e40931a454a2b3ad6b5490915e887af7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -52,6 +52,7 @@ plugins {
alias libs.plugins.android.application apply false
alias libs.plugins.kotlin.kapt apply false
alias libs.plugins.androidx.navigation.safeargs apply false
+ alias libs.plugins.android.library apply false
}
allprojects {
diff --git a/core/src/main/java/foundation/e/advancedprivacy/core/utils/NotificationsHelper.kt b/core/src/main/java/foundation/e/advancedprivacy/core/utils/NotificationsHelper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..29721b0bace329b5112b8f4ec64608ef1ba3705d
--- /dev/null
+++ b/core/src/main/java/foundation/e/advancedprivacy/core/utils/NotificationsHelper.kt
@@ -0,0 +1,35 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.core.utils
+
+import android.content.Context
+import androidx.core.app.NotificationCompat
+import foundation.e.advancedprivacy.domain.entities.NotificationContent
+
+fun notificationBuilder(
+ context: Context,
+ content: NotificationContent
+): NotificationCompat.Builder {
+ val builder = NotificationCompat.Builder(context, content.channelId)
+ .setSmallIcon(content.icon)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setContentTitle(context.getString(content.title))
+ .setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(content.description)))
+ content.pendingIntent?.let { builder.setContentIntent(it) }
+
+ return builder
+}
diff --git a/core/src/main/java/foundation/e/advancedprivacy/domain/entities/FeatureServiceState.kt b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/FeatureServiceState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6bfecbb928e750d38c419a6aad30dfa55748db90
--- /dev/null
+++ b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/FeatureServiceState.kt
@@ -0,0 +1,21 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.domain.entities
+
+enum class FeatureServiceState {
+ OFF, ON, STARTING, STOPPING
+}
diff --git a/core/src/main/java/foundation/e/advancedprivacy/domain/entities/NotificationChannels.kt b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/NotificationChannels.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4458e1df7b9c8745ad36bc0dae23993c60a88221
--- /dev/null
+++ b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/NotificationChannels.kt
@@ -0,0 +1,27 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.domain.entities
+
+const val CHANNEL_FIRST_BOOT = "first_boot_notification"
+const val CHANNEL_FAKE_LOCATION_FLAG = "fake_location_flag"
+const val CHANNEL_IPSCRAMBLING_FLAG = "ipscrambling_flag"
+const val CHANNEL_TRACKER_FLAG = "tracker_flag"
+
+const val NOTIFICATION_FIRST_BOOT = 1000
+const val NOTIFICATION_FAKE_LOCATION_FLAG = NOTIFICATION_FIRST_BOOT + 1
+const val NOTIFICATION_IPSCRAMBLING_FLAG = NOTIFICATION_FAKE_LOCATION_FLAG + 1
+const val NOTIFICATION_TRACKER_FLAG = NOTIFICATION_IPSCRAMBLING_FLAG + 1
diff --git a/core/src/main/java/foundation/e/advancedprivacy/domain/entities/NotificationContent.kt b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/NotificationContent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..44b508d0d1e381379cb95b15b11f1ed4dfad6284
--- /dev/null
+++ b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/NotificationContent.kt
@@ -0,0 +1,27 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.domain.entities
+
+import android.app.PendingIntent
+
+data class NotificationContent(
+ val channelId: String,
+ val icon: Int,
+ val title: Int,
+ val description: Int,
+ val pendingIntent: PendingIntent?
+)
diff --git a/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/APermissionsPrivacyModule.kt b/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleBase.kt
similarity index 98%
rename from core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/APermissionsPrivacyModule.kt
rename to core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleBase.kt
index 78f424b4ba6b1532df803d575bfe6e5c7fe35cd9..27ba17f55617f76ec5bafff7858448f2244821c7 100644
--- a/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/APermissionsPrivacyModule.kt
+++ b/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleBase.kt
@@ -37,7 +37,7 @@ import foundation.e.advancedprivacy.domain.entities.ProfileType
* versions of the module.
* @param context an Android context, to retrieve packageManager for example.
*/
-abstract class APermissionsPrivacyModule(protected val context: Context) : IPermissionsPrivacyModule {
+abstract class PermissionsPrivacyModuleBase(protected val context: Context) : IPermissionsPrivacyModule {
companion object {
private const val TAG = "PermissionsModule"
diff --git a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt
index f2e10a441c1f8bbd840f32da93090963aeef544a..c105ceb0d1e948c33a508d5c2520fd7f35c7a437 100644
--- a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt
+++ b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt
@@ -37,7 +37,7 @@ import foundation.e.advancedprivacy.domain.entities.AppOpModes
import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
import foundation.e.advancedprivacy.domain.entities.ProfileType
import foundation.e.advancedprivacy.fakelocation.domain.usecases.FakeLocationModule
-import foundation.e.advancedprivacy.permissions.externalinterfaces.PermissionsPrivacyModule
+import foundation.e.advancedprivacy.permissions.externalinterfaces.PermissionsPrivacyModuleImpl
import foundation.e.privacymodules.fakelocationdemo.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
@@ -46,7 +46,7 @@ class MainActivity : AppCompatActivity() {
}
private val fakeLocationModule: FakeLocationModule by lazy { FakeLocationModule(this) }
- private val permissionsModule by lazy { PermissionsPrivacyModule(this) }
+ private val permissionsModule by lazy { PermissionsPrivacyModuleImpl(this) }
private lateinit var binding: ActivityMainBinding
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index b9925d41dde5f01578fb08e6f987605248b06580..00bf753e7559800419f49148c193ed4d4bdc32fa 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -9,6 +9,7 @@ androidx-lifecycle = "2.5.0"
androidx-room = "2.3.0"
orbotservice = "orbot-16.6.3-1"
retrofit = "2.9.0"
+pcap4j = "1.8.2"
[libraries]
@@ -42,6 +43,8 @@ leakcanary = { group = "com.squareup.leakcanary", name = "leakcanary-android", v
maplibre = { group = "org.maplibre.gl", name = "android-sdk", version = "10.2.0" }
mockk = { group = "io.mockk", name = "mockk", version = "1.10.5" }
mpandroidcharts = { group = "com.github.PhilJay", name = "MPAndroidChart", version = "v3.1.0" }
+pcap4j = { group = "org.pcap4j", name = "pcap4j-core", version.ref = "pcap4j" }
+pcap4j-packetfactory-static = { group = "org.pcap4j", name = "pcap4j-packetfactory-static", version.ref = "pcap4j" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-scalars = { group = "com.squareup.retrofit2", name = "converter-scalars", version.ref = "retrofit" }
timber = { group = "com.jakewharton.timber", name = "timber", version = "5.0.1" }
@@ -50,10 +53,11 @@ timber = { group = "com.jakewharton.timber", name = "timber", version = "5.0.1"
koin = ["koin-core", "koin-android"]
kotlin-android-coroutines = ["androidx-core-ktx", "kotlinx-coroutines"]
-
+pcap4j = ["pcap4j", "pcap4j-packetfactory-static"]
[plugins]
android-application = { id = "com.android.application", version = "7.2.1" }
+android-library = { id = "com.android.library", version = "7.2.1" }
androidx-navigation-safeargs = { id = "androidx.navigation.safeargs.kotlin", version.ref = "androidx-navigation" }
benmanes-versions = { id = "com.github.ben-manes.versions", version = "0.38.0" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
diff --git a/permissionse/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModule.kt b/permissionse/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleImpl.kt
similarity index 98%
rename from permissionse/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModule.kt
rename to permissionse/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleImpl.kt
index 59a20dd724bc24bb28313fb963b9434d576321ea..0d32bce6ed526c7ad8fa8018fb27f2a0a73a399b 100644
--- a/permissionse/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModule.kt
+++ b/permissionse/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleImpl.kt
@@ -39,12 +39,12 @@ import foundation.e.advancedprivacy.domain.entities.AppOpModes
import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
import foundation.e.advancedprivacy.domain.entities.ProfileType.MAIN
import foundation.e.advancedprivacy.domain.entities.ProfileType.WORK
-import foundation.e.advancedprivacy.externalinterfaces.permissions.APermissionsPrivacyModule
+import foundation.e.advancedprivacy.externalinterfaces.permissions.PermissionsPrivacyModuleBase
/**
* Implements [IPermissionsPrivacyModule] with all privileges of a system app.
*/
-class PermissionsPrivacyModule(context: Context) : APermissionsPrivacyModule(context) {
+class PermissionsPrivacyModuleImpl(context: Context) : PermissionsPrivacyModuleBase(context) {
private val appOpsManager: AppOpsManager
get() = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
diff --git a/permissionsstandalone/src/main/java/foundation/e/advancedprivacy/permissions/externalinterfaces/PermissionsPrivacyModule.kt b/permissionsstandalone/src/main/java/foundation/e/advancedprivacy/permissions/externalinterfaces/PermissionsPrivacyModuleImpl.kt
similarity index 95%
rename from permissionsstandalone/src/main/java/foundation/e/advancedprivacy/permissions/externalinterfaces/PermissionsPrivacyModule.kt
rename to permissionsstandalone/src/main/java/foundation/e/advancedprivacy/permissions/externalinterfaces/PermissionsPrivacyModuleImpl.kt
index 95f5ff098a788df01ce902a1e27be22625d581ab..d31bdf441f2f3e135afcb5ce5fb4cbe4ab68ce8f 100644
--- a/permissionsstandalone/src/main/java/foundation/e/advancedprivacy/permissions/externalinterfaces/PermissionsPrivacyModule.kt
+++ b/permissionsstandalone/src/main/java/foundation/e/advancedprivacy/permissions/externalinterfaces/PermissionsPrivacyModuleImpl.kt
@@ -24,12 +24,12 @@ import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import foundation.e.advancedprivacy.domain.entities.AppOpModes
import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
-import foundation.e.advancedprivacy.externalinterfaces.permissions.APermissionsPrivacyModule
+import foundation.e.advancedprivacy.externalinterfaces.permissions.PermissionsPrivacyModuleBase
/**
* Implements [IPermissionsPrivacyModule] using only API authorized on the PlayStore.
*/
-class PermissionsPrivacyModule(context: Context) : APermissionsPrivacyModule(context) {
+class PermissionsPrivacyModuleImpl(context: Context) : PermissionsPrivacyModuleBase(context) {
override fun getApplications(
filter: ((PackageInfo) -> Boolean)?
): List {
diff --git a/settings.gradle b/settings.gradle
index 0b4940e5b0e7cceed2cdc65a2f95c37f75884758..39e58c8d881a885a74cd33f8e24395213d42188f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -18,7 +18,8 @@ include ':trackers'
include ':permissionse'
include ':permissionse:libs:hidden-apis-stub'
include ':ipscrambling'
-include ':ipscrambling:orbotservice'
+include ':trackersservicestandalone'
+include ':trackersservicee'
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
diff --git a/trackers/src/main/AndroidManifest.xml b/trackers/src/main/AndroidManifest.xml
index 615d310657480dbb4d898586a455733f58e9bc1f..b98070636d81833a33b8e422a87483a366ca47b2 100644
--- a/trackers/src/main/AndroidManifest.xml
+++ b/trackers/src/main/AndroidManifest.xml
@@ -18,21 +18,4 @@
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt
index 0cfb69cf050a3307ffe7a0d0d3be67fbda9a3bf8..34b4e7a863f6ae85a931acaa15efc06a1b1b5d5b 100644
--- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt
+++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt
@@ -21,13 +21,13 @@ import foundation.e.advancedprivacy.data.repositories.RemoteTrackersListReposito
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.FilterHostnameUseCase
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.core.qualifier.named
import org.koin.dsl.module
val trackersModule = module {
@@ -58,15 +58,13 @@ val trackersModule = module {
}
factory {
- DNSBlocker(
- context = androidContext(),
- trackersLogger = get(),
+ FilterHostnameUseCase(
trackersRepository = get(),
- whitelistRepository = get()
+ whitelistRepository = get(),
+ appDesc = get(named("AdvancedPrivacy")),
+ context = androidContext(),
+ database = get(),
+ appListsRepository = get()
)
}
-
- factory {
- TrackersLogger(statisticsUseCase = get())
- }
}
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
index c2c0768ee7cfeb433fa99d90cf127f3485cd874b..64477b7eabd50b360d6cca4d3a43132caff3d321 100644
--- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/RemoteTrackersListRepository.kt
+++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/RemoteTrackersListRepository.kt
@@ -39,7 +39,7 @@ class RemoteTrackersListRepository {
}
return true
} catch (e: IOException) {
- Timber.e("While saving tracker file.", e)
+ Timber.e(e, "While saving tracker file.")
}
return false
}
diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt
index 6aa76cfd2151f2b3f992718bfaba553000720156..15ff8137a687bc542e12c1fec53052a83d8a86cd 100644
--- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt
+++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt
@@ -32,6 +32,10 @@ import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.
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 kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.withContext
import timber.log.Timber
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
@@ -86,6 +90,9 @@ class StatsDatabase(
COLUMN_NAME_APPID
)
+ private val _newDataAvailable = MutableSharedFlow()
+ val newDataAvailable: SharedFlow = _newDataAvailable
+
private val lock = Any()
override fun onCreate(db: SQLiteDatabase) {
@@ -316,7 +323,9 @@ class StatsDatabase(
}
}
- fun logAccess(trackerId: String?, appId: String, blocked: Boolean) {
+ suspend fun logAccess(trackerId: String?, appId: String, blocked: Boolean) = withContext(
+ Dispatchers.IO
+ ) {
synchronized(lock) {
val currentHour = getCurrentHourTs()
val db = writableDatabase
@@ -364,6 +373,7 @@ class StatsDatabase(
cursor.close()
db.close()
}
+ _newDataAvailable.emit(Unit)
}
private fun cursorToEntry(cursor: Cursor): StatEntry {
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
index a7d5e49deaea8e016f56bc32b796681ff1b09a99..fc57a8ef8502cd0a26099d875eccbce9a6d7999a 100644
--- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/TrackersRepository.kt
+++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/TrackersRepository.kt
@@ -59,7 +59,7 @@ class TrackersRepository(
reader.close()
inputStream.close()
} catch (e: Exception) {
- Timber.e("While parsing trackers in assets", e)
+ Timber.e(e, "While parsing trackers in assets")
}
}
diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/externalinterfaces/TrackersServiceSupervisor.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/externalinterfaces/TrackersServiceSupervisor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d9674fc96764fbe9e952f2da7eaf615fb38bc948
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/externalinterfaces/TrackersServiceSupervisor.kt
@@ -0,0 +1,23 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.trackers.domain.externalinterfaces
+
+interface TrackersServiceSupervisor {
+ fun start(): Boolean
+ fun stop(): Boolean
+ fun isRunning(): Boolean
+}
diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/FilterHostnameUseCase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/FilterHostnameUseCase.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e229cab98775cab2393dd009a6f497d5057332db
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/FilterHostnameUseCase.kt
@@ -0,0 +1,108 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.trackers.domain.usecases
+
+import android.content.Context
+import android.content.pm.PackageManager
+import foundation.e.advancedprivacy.core.utils.runSuspendCatching
+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.data.TrackersRepository
+import foundation.e.advancedprivacy.trackers.data.WhitelistRepository
+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 FilterHostnameUseCase(
+ private val trackersRepository: TrackersRepository,
+ private val whitelistRepository: WhitelistRepository,
+ context: Context,
+ private val appDesc: ApplicationDescription,
+ private val database: StatsDatabase,
+ private val appListsRepository: AppListsRepository,
+) {
+ private var eBrowserAppUid = -1
+
+ companion object {
+ private const val E_BROWSER_DOT_SERVER = "chrome.cloudflare-dns.com"
+ }
+
+ init {
+ initEBrowserDoTFix(context)
+ }
+
+ fun shouldBlock(hostname: String, appUid: Int = appDesc.uid): Boolean {
+ var isBlocked = false
+
+ if (isEBrowserDoTBlockFix(appUid, hostname)) {
+ isBlocked = true
+ } else if (trackersRepository.isTracker(hostname)) {
+ val trackerId = trackersRepository.getTrackerId(hostname)
+ if (shouldBlock(appUid, trackerId)) {
+ isBlocked = true
+ }
+ queue.offer(DetectedTracker(trackerId, appUid, isBlocked))
+ }
+ return isBlocked
+ }
+
+ private fun initEBrowserDoTFix(context: Context) {
+ try {
+ eBrowserAppUid =
+ context.packageManager.getApplicationInfo("foundation.e.browser", 0).uid
+ } catch (e: PackageManager.NameNotFoundException) {
+ Timber.i("no E Browser package found.")
+ }
+ }
+
+ private fun isEBrowserDoTBlockFix(appUid: Int, hostname: String): Boolean {
+ return appUid == eBrowserAppUid &&
+ E_BROWSER_DOT_SERVER == hostname
+ }
+
+ private fun shouldBlock(appUid: Int, trackerId: String?): Boolean {
+ return whitelistRepository.isBlockingEnabled &&
+ !whitelistRepository.isWhiteListed(appUid, trackerId)
+ }
+
+ private val queue = LinkedBlockingQueue()
+
+ private suspend fun logAccess(detectedTracker: DetectedTracker) {
+ appListsRepository.getApp(detectedTracker.appUid)?.let { app ->
+ database.logAccess(detectedTracker.trackerId, app.apId, detectedTracker.wasBlocked)
+ }
+ }
+
+ fun writeLogJob(scope: CoroutineScope): Job {
+ return scope.launch(Dispatchers.IO) {
+ while (isActive) {
+ runSuspendCatching {
+ logAccess(queue.take())
+ }.onFailure {
+ Timber.e(it, "writeLogLoop detectedTrackersQueue.take() interrupted: ")
+ }
+ }
+ }
+ }
+
+ inner class DetectedTracker(var trackerId: String?, var appUid: Int, var wasBlocked: Boolean)
+}
diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt
index 55efeb99da82e3f56b18457fcdb2264e0a777f25..e7a84b8fa7ba3d03058b8fc7eddbb5c19a5470ac 100644
--- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt
+++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt
@@ -22,24 +22,12 @@ 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 StatisticsUseCase(
private val database: StatsDatabase,
private val appListsRepository: AppListsRepository
) {
- private val _newDataAvailable = MutableSharedFlow()
- val newDataAvailable: SharedFlow = _newDataAvailable
-
- suspend fun logAccess(trackerId: String?, appUid: Int, blocked: Boolean) {
- appListsRepository.getApp(appUid)?.let { app ->
- database.logAccess(trackerId, app.apId, blocked)
- _newDataAvailable.emit(Unit)
- }
- }
-
fun getTrackersCallsOnPeriod(
periodsCount: Int,
periodUnit: TemporalUnit
diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/TrackersLogger.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/TrackersLogger.kt
deleted file mode 100644
index 411b4ab606aaac4f19c3f56b3a8f7b058c265795..0000000000000000000000000000000000000000
--- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/TrackersLogger.kt
+++ /dev/null
@@ -1,60 +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 .
- */
-
-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(
- private val statisticsUseCase: StatisticsUseCase,
-) {
- private val queue = LinkedBlockingQueue()
-
- fun logAccess(trackerId: String?, appUid: Int, wasBlocked: Boolean) {
- queue.offer(DetectedTracker(trackerId, appUid, wasBlocked))
- }
-
- fun writeLogJob(scope: CoroutineScope): Job {
- return scope.launch(Dispatchers.IO) {
- while (isActive) {
- runSuspendCatching {
- logAccess(queue.take())
- }.onFailure {
- Timber.e(it, "writeLogLoop detectedTrackersQueue.take() interrupted: ")
- }
- }
- }
- }
-
- private suspend fun logAccess(detectedTracker: DetectedTracker) {
- statisticsUseCase.logAccess(
- detectedTracker.trackerId,
- detectedTracker.appUid,
- detectedTracker.wasBlocked
- )
- }
-
- inner class DetectedTracker(var trackerId: String?, var appUid: Int, var wasBlocked: Boolean)
-}
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
index 55da6449a062e5513bd9d08548e2c4b58250421b..fa60431cb7475340b587aeeb9ed37d72f294fd71 100644
--- 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
@@ -40,7 +40,7 @@ class UpdateTrackerListUseCase(
remoteTrackersListRepository.saveData(trackersRepository.eTrackerFile, api.trackers())
trackersRepository.initTrackersFile()
} catch (e: Exception) {
- Timber.e("While updating trackers", e)
+ Timber.e(e, "While updating trackers")
}
}
}
diff --git a/trackersservicee/.gitignore b/trackersservicee/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42afabfd2abebf31384ca7797186a27a4b7dbee8
--- /dev/null
+++ b/trackersservicee/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/trackersservicee/build.gradle b/trackersservicee/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..f7725bfb40b02f6674d2141a960949da09392613
--- /dev/null
+++ b/trackersservicee/build.gradle
@@ -0,0 +1,43 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ namespace 'foundation.e.advancedprivacy.trackers.service'
+ compileSdk 33
+
+ defaultConfig {
+ minSdk 24
+ targetSdk 33
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+ implementation project(":core")
+ implementation project(":trackers")
+
+ implementation(
+ libs.androidx.core.ktx,
+ libs.bundles.koin,
+ libs.kotlinx.coroutines,
+ libs.timber,
+ )
+}
diff --git a/trackersservicee/consumer-rules.pro b/trackersservicee/consumer-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/trackersservicee/proguard-rules.pro b/trackersservicee/proguard-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce
--- /dev/null
+++ b/trackersservicee/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/trackersservicee/src/main/AndroidManifest.xml b/trackersservicee/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..22904321c585cae229f15f6be0e799d597ae043c
--- /dev/null
+++ b/trackersservicee/src/main/AndroidManifest.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/DNSBlocker.kt b/trackersservicee/src/main/java/foundation/e/advancedprivacy/trackers/service/DNSBlocker.kt
similarity index 65%
rename from trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/DNSBlocker.kt
rename to trackersservicee/src/main/java/foundation/e/advancedprivacy/trackers/service/DNSBlocker.kt
index fb089103d4183d085da0a4c29eb03a6f55391bf0..6a2b2189bcd2c52ff53194e695fb9ad3087433a3 100644
--- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/DNSBlocker.kt
+++ b/trackersservicee/src/main/java/foundation/e/advancedprivacy/trackers/service/DNSBlocker.kt
@@ -16,17 +16,14 @@
* along with this program. If not, see .
*/
-package foundation.e.advancedprivacy.trackers.domain.usecases
+package foundation.e.advancedprivacy.trackers.service
-import android.content.Context
-import android.content.pm.PackageManager
import android.net.LocalServerSocket
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import foundation.e.advancedprivacy.core.utils.runSuspendCatching
-import foundation.e.advancedprivacy.trackers.data.TrackersRepository
-import foundation.e.advancedprivacy.trackers.data.WhitelistRepository
+import foundation.e.advancedprivacy.trackers.domain.usecases.FilterHostnameUseCase
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -39,21 +36,12 @@ import java.io.InputStreamReader
import java.io.PrintWriter
class DNSBlocker(
- context: Context,
- val trackersLogger: TrackersLogger,
- private val trackersRepository: TrackersRepository,
- private val whitelistRepository: WhitelistRepository
+ val filterHostnameUseCase: FilterHostnameUseCase
) {
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"
- }
-
- init {
- initEBrowserDoTFix(context)
}
private fun closeSocket() {
@@ -97,18 +85,9 @@ class DNSBlocker(
val writer = PrintWriter(output, true)
val domainName = params[0]
val appUid = params[1].toInt()
- var isBlocked = false
- if (isEBrowserDoTBlockFix(appUid, domainName)) {
- isBlocked = true
- } else if (trackersRepository.isTracker(domainName)) {
- val trackerId = trackersRepository.getTrackerId(domainName)
- if (shouldBlock(appUid, trackerId)) {
- writer.println("block")
- isBlocked = true
- }
- trackersLogger.logAccess(trackerId, appUid, isBlocked)
- }
- if (!isBlocked) {
+ if (filterHostnameUseCase.shouldBlock(domainName, appUid)) {
+ writer.println("block")
+ } else {
writer.println("pass")
}
socket.close()
@@ -122,22 +101,4 @@ class DNSBlocker(
}
}
}
-
- private fun initEBrowserDoTFix(context: Context) {
- try {
- eBrowserAppUid =
- context.packageManager.getApplicationInfo("foundation.e.browser", 0).uid
- } catch (e: PackageManager.NameNotFoundException) {
- Timber.i(e, "no E Browser package found.")
- }
- }
-
- private fun isEBrowserDoTBlockFix(appUid: Int, hostname: String): Boolean {
- return appUid == eBrowserAppUid && E_BROWSER_DOT_SERVER == hostname
- }
-
- private fun shouldBlock(appUid: Int, trackerId: String?): Boolean {
- return whitelistRepository.isBlockingEnabled &&
- !whitelistRepository.isWhiteListed(appUid, trackerId)
- }
}
diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/DNSBlockerService.kt b/trackersservicee/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersService.kt
similarity index 71%
rename from trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/DNSBlockerService.kt
rename to trackersservicee/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersService.kt
index 25539e18777121008c527c8cbe55578b2740d60e..5f573b0fa9d5d4fa8e3e283870e4e3cd24ead7e6 100644
--- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/DNSBlockerService.kt
+++ b/trackersservicee/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersService.kt
@@ -15,27 +15,22 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
-package foundation.e.advancedprivacy.trackers.services
+package foundation.e.advancedprivacy.trackers.service
import android.app.Service
import android.content.Intent
import android.os.IBinder
-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() {
+class TrackersService : Service() {
companion object {
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
+ var coroutineScope = CoroutineScope(Dispatchers.IO)
+ }
override fun onBind(intent: Intent): IBinder? {
throw UnsupportedOperationException("Not yet implemented")
@@ -43,9 +38,6 @@ class DNSBlockerService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (ACTION_START == intent?.action) {
- if (intent.getBooleanExtra(EXTRA_ENABLE_NOTIFICATION, true)) {
- ForegroundStarter.startForeground(this)
- }
stop()
start()
}
@@ -55,14 +47,12 @@ class DNSBlockerService : Service() {
private fun start() {
coroutineScope = CoroutineScope(Dispatchers.IO)
get(DNSBlocker::class.java).apply {
- this@DNSBlockerService.dnsBlocker = this
- trackersLogger.writeLogJob(coroutineScope)
+ filterHostnameUseCase.writeLogJob(coroutineScope)
listenJob(coroutineScope)
}
}
private fun stop() {
kotlin.runCatching { coroutineScope.cancel() }
- dnsBlocker = null
}
}
diff --git a/trackersservicee/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersServiceSupervisorImpl.kt b/trackersservicee/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersServiceSupervisorImpl.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3903db48cecd5ef23a6bc7b35f4a182b5bc16bc1
--- /dev/null
+++ b/trackersservicee/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersServiceSupervisorImpl.kt
@@ -0,0 +1,46 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.trackers.service
+
+import android.content.Context
+import android.content.Intent
+import foundation.e.advancedprivacy.trackers.domain.externalinterfaces.TrackersServiceSupervisor
+import foundation.e.advancedprivacy.trackers.service.TrackersService.Companion.ACTION_START
+import kotlinx.coroutines.isActive
+import org.koin.core.module.dsl.factoryOf
+import org.koin.dsl.module
+
+class TrackersServiceSupervisorImpl(private val context: Context) : TrackersServiceSupervisor {
+
+ override fun start(): Boolean {
+ val intent = Intent(context, TrackersService::class.java)
+ intent.action = ACTION_START
+ return context.startService(intent) != null
+ }
+
+ override fun stop(): Boolean {
+ return context.stopService(Intent(context, TrackersService::class.java))
+ }
+
+ override fun isRunning(): Boolean {
+ return TrackersService.coroutineScope.isActive
+ }
+}
+
+val trackerServiceModule = module {
+ factoryOf(::DNSBlocker)
+}
diff --git a/trackersservicestandalone/.gitignore b/trackersservicestandalone/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42afabfd2abebf31384ca7797186a27a4b7dbee8
--- /dev/null
+++ b/trackersservicestandalone/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/trackersservicestandalone/build.gradle b/trackersservicestandalone/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..d5df422cef4245ca9145e38086e4bd2eda622056
--- /dev/null
+++ b/trackersservicestandalone/build.gradle
@@ -0,0 +1,43 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdk 32
+
+ defaultConfig {
+ minSdk 26
+ targetSdk 32
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+ implementation project(":core")
+ implementation project(":trackers")
+
+ implementation(
+ libs.androidx.core.ktx,
+ libs.bundles.koin,
+ libs.bundles.pcap4j,
+ libs.kotlinx.coroutines,
+ libs.timber,
+ )
+}
diff --git a/trackersservicestandalone/consumer-rules.pro b/trackersservicestandalone/consumer-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/trackersservicestandalone/proguard-rules.pro b/trackersservicestandalone/proguard-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce
--- /dev/null
+++ b/trackersservicestandalone/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/trackersservicestandalone/src/main/AndroidManifest.xml b/trackersservicestandalone/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4bfa4eb0292be2fccbeb6c381b1f1bb879f94982
--- /dev/null
+++ b/trackersservicestandalone/src/main/AndroidManifest.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/Config.kt b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/Config.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d079e22e5f427e783ab0cadc6faf5c408658d9fb
--- /dev/null
+++ b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/Config.kt
@@ -0,0 +1,44 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.trackers.service
+
+internal object Config {
+ const val SESSION_NAME = "TrackersService"
+
+ const val FALLBACK_DNS = "1.1.1.1"
+ const val VERBOSE = true
+
+ const val VIRTUALDNS_IPV4 = "10.10.10.10"
+ const val VIRTUALDNS_IPV6 = "fdc8:1095:91e1:aaaa:aaaa:aaaa:aaaa:aaa1"
+ const val ADDRESS_IPV4 = "10.0.2.15"
+ const val ADDRESS_IPV6 = "fdc8:1095:91e1:aaaa:aaaa:aaaa:aaaa:aaa2"
+
+ const val BLOCKED_IPV4 = "127.0.0.1"
+ const val BLOCKED_IPV6 = "::1"
+
+ const val MTU = 3000
+ const val LOCAL_RESOLVER_TTL = 60
+
+ const val MAX_RESOLVER_COUNT = 100
+
+ val DNS_SERVER_TO_CATCH_IPV4 = listOf(
+ "8.8.8.8", "8.8.4.4", "1.1.1.1"
+ )
+ val DNS_SERVER_TO_CATCH_IPV6 = listOf(
+ "2001:4860:4860::8888", "2001:4860:4860::8844"
+ )
+}
diff --git a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersService.kt b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..918977f677a8ec33b6274dd86993ece2664a77b6
--- /dev/null
+++ b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersService.kt
@@ -0,0 +1,127 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.trackers.service
+
+import android.content.Context
+import android.content.Intent
+import android.net.VpnService
+import android.os.Build
+import android.os.ParcelFileDescriptor
+import foundation.e.advancedprivacy.core.utils.notificationBuilder
+import foundation.e.advancedprivacy.domain.entities.FeatureServiceState
+import foundation.e.advancedprivacy.domain.entities.NOTIFICATION_TRACKER_FLAG
+import foundation.e.advancedprivacy.domain.entities.NotificationContent
+import foundation.e.advancedprivacy.trackers.domain.externalinterfaces.TrackersServiceSupervisor
+import foundation.e.advancedprivacy.trackers.service.Config.DNS_SERVER_TO_CATCH_IPV4
+import foundation.e.advancedprivacy.trackers.service.Config.DNS_SERVER_TO_CATCH_IPV6
+import foundation.e.advancedprivacy.trackers.service.Config.SESSION_NAME
+import foundation.e.advancedprivacy.trackers.service.data.NetworkDNSAddressRepository
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import org.koin.core.qualifier.named
+import org.koin.java.KoinJavaComponent.get
+import timber.log.Timber
+
+class TrackersService : VpnService() {
+ companion object {
+ var coroutineScope = CoroutineScope(Dispatchers.IO)
+
+ fun start(context: Context) {
+ prepare(context)
+ val intent = Intent(context, TrackersService::class.java)
+ context.startService(intent)
+ }
+ }
+
+ private val networkDNSAddressRepository: NetworkDNSAddressRepository = get(NetworkDNSAddressRepository::class.java)
+ private val trackersServiceSupervisor: TrackersServiceSupervisorImpl = get(
+ TrackersServiceSupervisor::class.java
+ ) as TrackersServiceSupervisorImpl
+
+ private val notificationTrackerFlag: NotificationContent = get(NotificationContent::class.java, named("notificationTrackerFlag"))
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ startVPN()
+
+ startForeground(
+ NOTIFICATION_TRACKER_FLAG,
+ notificationBuilder(
+ context = this,
+ content = notificationTrackerFlag
+ ).build()
+ )
+ trackersServiceSupervisor.state.value = FeatureServiceState.ON
+
+ return START_STICKY
+ }
+
+ override fun onDestroy() {
+ networkDNSAddressRepository.stop()
+ trackersServiceSupervisor.state.value = FeatureServiceState.OFF
+ super.onDestroy()
+ }
+
+ private fun startVPN() {
+ val vpnInterface = initVPN()
+
+ if (vpnInterface != null) {
+ networkDNSAddressRepository.start()
+
+ coroutineScope = CoroutineScope(Dispatchers.IO)
+ get(TunLooper::class.java).apply {
+ listenJob(vpnInterface, coroutineScope)
+ }
+ } else {
+ Timber.e("Cannot get VPN interface")
+ }
+ }
+
+ private fun initVPN(): ParcelFileDescriptor? {
+ val builder = Builder()
+ builder.setSession(SESSION_NAME)
+ // IPV4:
+ builder
+ .addAddress(Config.ADDRESS_IPV4, 24)
+ .addDnsServer(Config.VIRTUALDNS_IPV4)
+ .addRoute(Config.VIRTUALDNS_IPV4, 32)
+
+ // IPV6
+ builder
+ .addAddress(Config.ADDRESS_IPV6, 48)
+ .addDnsServer(Config.VIRTUALDNS_IPV6)
+ .addRoute(Config.VIRTUALDNS_IPV6, 128)
+
+ DNS_SERVER_TO_CATCH_IPV4.forEach {
+ builder.addRoute(it, 32)
+ }
+ DNS_SERVER_TO_CATCH_IPV6.forEach {
+ builder.addRoute(it, 128)
+ }
+
+ // TODO: block private DNS.
+ // TODO 20230821: seen in privateDNSFilter, bypass filter for google apps on Android 7/8
+
+ builder.addDisallowedApplication(packageName)
+ builder.setBlocking(true)
+ builder.setMtu(Config.MTU)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ builder.setMetered(false) // take over defaults from underlying network
+ }
+
+ return builder.establish()
+ }
+}
diff --git a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersServiceSupervisorImpl.kt b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersServiceSupervisorImpl.kt
new file mode 100644
index 0000000000000000000000000000000000000000..25d3e2d46e3cd9fce4f09d6425c89240feeceb8b
--- /dev/null
+++ b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersServiceSupervisorImpl.kt
@@ -0,0 +1,64 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.trackers.service
+
+import android.content.Context
+import android.content.Intent
+import foundation.e.advancedprivacy.domain.entities.FeatureServiceState
+import foundation.e.advancedprivacy.trackers.domain.externalinterfaces.TrackersServiceSupervisor
+import foundation.e.advancedprivacy.trackers.service.data.NetworkDNSAddressRepository
+import foundation.e.advancedprivacy.trackers.service.data.RequestDNSRepository
+import foundation.e.advancedprivacy.trackers.service.usecases.ResolveDNSUseCase
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.koin.core.module.dsl.singleOf
+import org.koin.dsl.module
+
+class TrackersServiceSupervisorImpl(private val context: Context) : TrackersServiceSupervisor {
+ internal val state: MutableStateFlow = MutableStateFlow(FeatureServiceState.OFF)
+
+ override fun start(): Boolean {
+ return if (!isRunning()) {
+ state.value = FeatureServiceState.STARTING
+ TrackersService.start(context)
+ true
+ } else false
+ }
+
+ override fun stop(): Boolean {
+ return when (state.value) {
+ FeatureServiceState.ON -> {
+ state.value = FeatureServiceState.STOPPING
+ kotlin.runCatching { TrackersService.coroutineScope.cancel() }
+ context.stopService(Intent(context, TrackersService::class.java))
+ true
+ }
+ else -> false
+ }
+ }
+
+ override fun isRunning(): Boolean {
+ return state.value != FeatureServiceState.OFF
+ }
+}
+
+val trackerServiceModule = module {
+ singleOf(::NetworkDNSAddressRepository)
+ singleOf(::RequestDNSRepository)
+ singleOf(::ResolveDNSUseCase)
+ singleOf(::TunLooper)
+}
diff --git a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TunLooper.kt b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TunLooper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7813c67487563136ab0d083afc09917afca5dc06
--- /dev/null
+++ b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TunLooper.kt
@@ -0,0 +1,167 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.trackers.service
+
+import android.os.ParcelFileDescriptor
+import foundation.e.advancedprivacy.trackers.service.usecases.ResolveDNSUseCase
+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 org.pcap4j.packet.DnsPacket
+import org.pcap4j.packet.IpPacket
+import org.pcap4j.packet.IpSelector
+import org.pcap4j.packet.IpV4Packet
+import org.pcap4j.packet.IpV6Packet
+import org.pcap4j.packet.UdpPacket
+import org.pcap4j.packet.namednumber.IpNumber
+import org.pcap4j.packet.namednumber.UdpPort
+import timber.log.Timber
+import java.io.DataOutputStream
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.IOException
+import java.net.Inet6Address
+import java.util.Arrays
+
+class TunLooper(
+ private val resolveDNSUseCase: ResolveDNSUseCase,
+) {
+ private var vpnInterface: ParcelFileDescriptor? = null
+ private var fileInputStream: FileInputStream? = null
+ private var dataOutputStream: DataOutputStream? = null
+
+ private fun closeStreams() {
+ fileInputStream?.close()
+ fileInputStream = null
+
+ dataOutputStream?.close()
+ dataOutputStream = null
+
+ vpnInterface?.close()
+ vpnInterface = null
+ }
+
+ fun listenJob(
+ vpnInterface: ParcelFileDescriptor,
+ scope: CoroutineScope
+ ): Job = scope.launch(Dispatchers.IO) {
+ this@TunLooper.vpnInterface = vpnInterface
+ val fis = FileInputStream(vpnInterface.fileDescriptor)
+ this@TunLooper.fileInputStream = fis
+ dataOutputStream = DataOutputStream(FileOutputStream(vpnInterface.fileDescriptor))
+
+ while (isActive) {
+ runCatching {
+ val buffer = ByteArray(Config.MTU)
+ val pLen = fis.read(buffer)
+
+ if (pLen > 0) {
+ scope.launch { handleIpPacket(buffer, pLen) }
+ }
+ }.onFailure {
+ if (it is CancellationException) {
+ closeStreams()
+ throw it
+ } else {
+ Timber.w(it, "while reading from VPN fd")
+ }
+ }
+ }
+ }
+
+ private suspend fun handleIpPacket(buffer: ByteArray, pLen: Int) {
+ val pdata = Arrays.copyOf(buffer, pLen)
+ try {
+ val packet = IpSelector.newPacket(pdata, 0, pdata.size)
+ if (packet is IpPacket) {
+ val ipPacket = packet
+ if (isPacketDNS(ipPacket)) {
+ handleDnsPacket(ipPacket)
+ }
+ }
+ } catch (e: Exception) {
+ Timber.w(e, "Can't parse packet, ignore it.")
+ }
+ }
+
+ private fun isPacketDNS(p: IpPacket): Boolean {
+ if (p.header.protocol === IpNumber.UDP) {
+ val up = p.payload as UdpPacket
+ return up.header.dstPort === UdpPort.DOMAIN
+ }
+ return false
+ }
+
+ private suspend fun handleDnsPacket(ipPacket: IpPacket) {
+ try {
+ val udpPacket = ipPacket.payload as UdpPacket
+ val dnsRequest = udpPacket.payload as DnsPacket
+ val dnsResponse = resolveDNSUseCase.processDNS(dnsRequest)
+
+ if (dnsResponse != null) {
+ val dnsBuilder = dnsResponse.builder
+
+ val udpBuilder = UdpPacket.Builder(udpPacket)
+ .srcPort(udpPacket.header.dstPort)
+ .dstPort(udpPacket.header.srcPort)
+ .srcAddr(ipPacket.getHeader().getDstAddr())
+ .dstAddr(ipPacket.getHeader().getSrcAddr())
+ .correctChecksumAtBuild(true)
+ .correctLengthAtBuild(true)
+ .payloadBuilder(dnsBuilder)
+
+ val respPacket: IpPacket? = if (ipPacket is IpV4Packet) {
+ val ipV4Packet = ipPacket
+ val ipv4Builder = IpV4Packet.Builder()
+ ipv4Builder
+ .version(ipV4Packet.header.version)
+ .protocol(ipV4Packet.header.protocol)
+ .tos(ipV4Packet.header.tos)
+ .srcAddr(ipV4Packet.header.dstAddr)
+ .dstAddr(ipV4Packet.header.srcAddr)
+ .correctChecksumAtBuild(true)
+ .correctLengthAtBuild(true)
+ .dontFragmentFlag(ipV4Packet.header.dontFragmentFlag)
+ .reservedFlag(ipV4Packet.header.reservedFlag)
+ .moreFragmentFlag(ipV4Packet.header.moreFragmentFlag)
+ .ttl(Integer.valueOf(64).toByte())
+ .payloadBuilder(udpBuilder)
+ ipv4Builder.build()
+ } else if (ipPacket is IpV6Packet) {
+ IpV6Packet.Builder(ipPacket as IpV6Packet?)
+ .srcAddr(ipPacket.getHeader().getDstAddr() as Inet6Address)
+ .dstAddr(ipPacket.getHeader().getSrcAddr() as Inet6Address)
+ .payloadBuilder(udpBuilder)
+ .build()
+ } else null
+
+ respPacket?.let {
+ try {
+ dataOutputStream?.write(it.rawData)
+ } catch (e: IOException) {
+ Timber.e(e, "error writing to VPN fd")
+ }
+ }
+ }
+ } catch (ioe: java.lang.Exception) {
+ Timber.e(ioe, "could not parse DNS packet")
+ }
+ }
+}
diff --git a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/data/NetworkDNSAddressRepository.kt b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/data/NetworkDNSAddressRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7c36ed2e4170432e5a03f8b2a82f1f94b791d077
--- /dev/null
+++ b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/data/NetworkDNSAddressRepository.kt
@@ -0,0 +1,59 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.trackers.service.data
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkRequest
+import foundation.e.advancedprivacy.trackers.service.Config
+import java.net.InetAddress
+
+class NetworkDNSAddressRepository(private val context: Context) {
+ private val connectivityManager: ConnectivityManager =
+ context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+
+ fun start() {
+ connectivityManager.registerNetworkCallback(
+ NetworkRequest.Builder().build(),
+ networkCallback
+ )
+ }
+
+ fun stop() {
+ kotlin.runCatching {
+ connectivityManager.unregisterNetworkCallback(networkCallback)
+ }
+ }
+
+ var dnsAddress: InetAddress = InetAddress.getByName(Config.FALLBACK_DNS)
+ private set
+
+ private val networkCallback = object : ConnectivityManager.NetworkCallback() {
+ override fun onAvailable(network: Network) {
+ super.onAvailable(network)
+ connectivityManager.getLinkProperties(network)
+ ?.dnsServers?.firstOrNull {
+ it.hostAddress.let {
+ it != Config.VIRTUALDNS_IPV4 && it != Config.VIRTUALDNS_IPV6
+ }
+ }?.let {
+ dnsAddress = InetAddress.getByName(it.hostAddress)
+ }
+ }
+ }
+}
diff --git a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/data/RequestDNSRepository.kt b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/data/RequestDNSRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d9370be529b432acfc850a7c53c37765d87b69f6
--- /dev/null
+++ b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/data/RequestDNSRepository.kt
@@ -0,0 +1,48 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.trackers.service.data
+
+import foundation.e.advancedprivacy.core.utils.runSuspendCatching
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.pcap4j.packet.DnsPacket
+import timber.log.Timber
+import java.net.DatagramPacket
+import java.net.DatagramSocket
+
+class RequestDNSRepository {
+
+ suspend fun processDNS(request: DatagramPacket): DnsPacket? = withContext(Dispatchers.IO) {
+ runSuspendCatching {
+ var response: DnsPacket? = null
+ val datagramSocket = DatagramSocket()
+ datagramSocket.send(request)
+
+ // Await response from DNS server
+ val buf = ByteArray(1024)
+ val packet = DatagramPacket(buf, buf.size)
+ datagramSocket.receive(packet)
+ val dnsResp = packet.data
+ if (dnsResp != null) {
+ response = DnsPacket.newPacket(dnsResp, 0, dnsResp.size)
+ }
+ response
+ }.onFailure {
+ Timber.w(it, "Can't make DNS request.")
+ }.getOrNull()
+ }
+}
diff --git a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/usecases/ResolveDNSUseCase.kt b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/usecases/ResolveDNSUseCase.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ac8aee03fb9752d7438a1372f59623a098f84e2b
--- /dev/null
+++ b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/usecases/ResolveDNSUseCase.kt
@@ -0,0 +1,54 @@
+/*
+ * 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 .
+ */
+package foundation.e.advancedprivacy.trackers.service.usecases
+
+import foundation.e.advancedprivacy.trackers.domain.usecases.FilterHostnameUseCase
+import foundation.e.advancedprivacy.trackers.service.data.NetworkDNSAddressRepository
+import foundation.e.advancedprivacy.trackers.service.data.RequestDNSRepository
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.GlobalScope
+import org.pcap4j.packet.DnsPacket
+import org.pcap4j.packet.namednumber.DnsRCode
+import java.net.DatagramPacket
+
+@OptIn(DelicateCoroutinesApi::class)
+class ResolveDNSUseCase(
+ private val networkDNSAddressRepository: NetworkDNSAddressRepository,
+ private val filterHostnameUseCase: FilterHostnameUseCase,
+ private val requestDNSRepository: RequestDNSRepository,
+ private val scope: CoroutineScope = GlobalScope
+) {
+ private val DNS_PORT = 53
+
+ init {
+ filterHostnameUseCase.writeLogJob(scope)
+ }
+
+ suspend fun processDNS(dnsRequest: DnsPacket): DnsPacket? {
+ val host = dnsRequest.header.questions[0].qName.name
+ if (filterHostnameUseCase.shouldBlock(host)) {
+ return dnsRequest.builder
+ .rCode(DnsRCode.NX_DOMAIN)
+ .response(true).build()
+ }
+
+ val payload = dnsRequest.rawData
+ val packet = DatagramPacket(payload, payload.size, networkDNSAddressRepository.dnsAddress, DNS_PORT)
+ return requestDNSRepository.processDNS(packet)
+ }
+}