diff options
author | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2023-10-11 16:36:02 +0000 |
---|---|---|
committer | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2023-10-11 16:36:02 +0000 |
commit | 9666ac2127df25f7b9d761474e72b892e40a6af1 (patch) | |
tree | 7047816719c824c79c6d40371d7eb7b944ad6db0 | |
parent | 8b85d2b8ee8d5373dd0cd60bcf18290a8c854467 (diff) | |
parent | 95e68cbbe748f81af1113753c5b99929e3db9ea2 (diff) |
Merge branch 'epic18-standalone_trackers_alone' into 'main'
epic18: Trackers control on standalone app (without Ipscrambling).
See merge request e/os/advanced-privacy!147
65 files changed, 1187 insertions, 457 deletions
@@ -6,21 +6,7 @@ build/ local.properties # IntelliJ .idea folder -/.idea/caches -/.idea/libraries -/.idea/misc.xml -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -.idea/tasks.xml -.idea/compiler.xml -/.idea/assetWizardSettings.xml -/.idea/jarRepositories.xml -/.idea/google-java-format.xml -/.idea/runConfigurations.xml -/.idea/dbnavigator.xml -/.idea/deploymentTargetDropDown.xml -/.idea/kotlinc.xml +.idea/ gradle.xml markdown-*.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 5f54997..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -AdvancedPrivacy diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 97c1063..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,167 +0,0 @@ -<component name="ProjectCodeStyleConfiguration"> - <code_scheme name="Project" version="173"> - <option name="FORMATTER_TAGS_ENABLED" value="true" /> - <AndroidXmlCodeStyleSettings> - <option name="LAYOUT_SETTINGS"> - <value> - <option name="INSERT_LINE_BREAK_AFTER_LAST_ATTRIBUTE" value="true" /> - </value> - </option> - <option name="MANIFEST_SETTINGS"> - <value> - <option name="INSERT_LINE_BREAK_AFTER_LAST_ATTRIBUTE" value="true" /> - </value> - </option> - <option name="OTHER_SETTINGS"> - <value> - <option name="INSERT_LINE_BREAK_AFTER_LAST_ATTRIBUTE" value="true" /> - </value> - </option> - </AndroidXmlCodeStyleSettings> - <JetCodeStyleSettings> - <option name="PACKAGES_TO_USE_STAR_IMPORTS"> - <value> - <package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" /> - </value> - </option> - <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" /> - <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" /> - <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> - </JetCodeStyleSettings> - <MarkdownNavigatorCodeStyleSettings> - <option name="RIGHT_MARGIN" value="72" /> - </MarkdownNavigatorCodeStyleSettings> - <codeStyleSettings language="Groovy"> - <indentOptions> - <option name="CONTINUATION_INDENT_SIZE" value="4" /> - </indentOptions> - </codeStyleSettings> - <codeStyleSettings language="XML"> - <indentOptions> - <option name="CONTINUATION_INDENT_SIZE" value="4" /> - </indentOptions> - <arrangement> - <rules> - <section> - <rule> - <match> - <AND> - <NAME>xmlns:android</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>xmlns:.*</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - <order>BY_NAME</order> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*:id</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>http://schemas.android.com/apk/res/android\n</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*:name</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>http://schemas.android.com/apk/res/android\n</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>name</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>style</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - <order>BY_NAME</order> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>http://schemas.android.com/apk/res/android\n</XML_NAMESPACE> - </AND> - </match> - <order>ANDROID_ATTRIBUTE_ORDER</order> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>.*</XML_NAMESPACE> - </AND> - </match> - <order>BY_NAME</order> - </rule> - </section> - </rules> - </arrangement> - </codeStyleSettings> - <codeStyleSettings language="kotlin"> - <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> - <option name="RIGHT_MARGIN" value="100" /> - <option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" /> - <option name="LINE_COMMENT_ADD_SPACE" value="true" /> - <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" /> - <option name="KEEP_BLANK_LINES_IN_CODE" value="1" /> - <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" /> - <option name="ALIGN_MULTILINE_PARAMETERS" value="false" /> - <option name="FIELD_ANNOTATION_WRAP" value="1" /> - <option name="VARIABLE_ANNOTATION_WRAP" value="1" /> - <indentOptions> - <option name="CONTINUATION_INDENT_SIZE" value="4" /> - </indentOptions> - </codeStyleSettings> - </code_scheme> -</component>
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 0f7bc51..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ -<component name="ProjectCodeStyleConfiguration"> - <state> - <option name="USE_PER_PROJECT_SETTINGS" value="true" /> - </state> -</component> diff --git a/.idea/copyright/MURENA.xml b/.idea/copyright/MURENA.xml deleted file mode 100644 index 0c0ee95..0000000 --- a/.idea/copyright/MURENA.xml +++ /dev/null @@ -1,6 +0,0 @@ -<component name="CopyrightManager"> - <copyright> - <option name="notice" value="Copyright (C) &#36;today.year 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/>." /> - <option name="myName" value="MURENA" /> - </copyright> -</component>
\ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e9f8efd..0000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ -<component name="CopyrightManager"> - <settings default="E_Foundation" /> -</component>
\ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 97626ba..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="Encoding"> - <file url="PROJECT" charset="UTF-8" /> - </component> -</project>
\ No newline at end of file diff --git a/.idea/inspectionProfiles/ktlint.xml b/.idea/inspectionProfiles/ktlint.xml deleted file mode 100644 index 7d04a74..0000000 --- a/.idea/inspectionProfiles/ktlint.xml +++ /dev/null @@ -1,7 +0,0 @@ -<component name="InspectionProjectProfileManager"> - <profile version="1.0"> - <option name="myName" value="ktlint" /> - <inspection_tool class="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" /> - <inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" /> - </profile> -</component> diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 64580d1..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<component name="InspectionProjectProfileManager"> - <settings> - <option name="PROJECT_PROFILE" value="ktlint" /> - <version value="1.0" /> - </settings> -</component> diff --git a/.idea/runConfigurations/Build_system_app.xml b/.idea/runConfigurations/Build_system_app.xml deleted file mode 100644 index 755d57a..0000000 --- a/.idea/runConfigurations/Build_system_app.xml +++ /dev/null @@ -1,13 +0,0 @@ -<component name="ProjectRunConfigurationManager"> - <configuration default="false" name="Build system app" type="ShConfigurationType"> - <option name="INDEPENDENT_SCRIPT_PATH" value="true" /> - <option name="SCRIPT_PATH" value="$PROJECT_DIR$/scripts/sign_and_push.sh" /> - <option name="SCRIPT_OPTIONS" value="" /> - <option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" /> - <option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" /> - <option name="INDEPENDENT_INTERPRETER_PATH" value="true" /> - <option name="INTERPRETER_PATH" value="/bin/bash" /> - <option name="INTERPRETER_OPTIONS" value="" /> - <method v="2" /> - </configuration> -</component>
\ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="VcsDirectoryMappings"> - <mapping directory="$PROJECT_DIR$" vcs="Git" /> - </component> -</project>
\ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index ef54d45..216b81a 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 0af2a0e..71fef00 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>(IpScramblingStateUseCase::class.java) - get<FakeLocationStateUseCase>(FakeLocationStateUseCase::class.java) get<TrackersStateUseCase>(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>(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 0000000..e69de29 --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt diff --git a/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt b/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt index 3fbb636..20cefd5 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<Resources> { androidContext().resources } single { @@ -89,6 +94,16 @@ val appModule = module { ) } + single<NotificationContent>(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<IPermissionsPrivacyModule> { - PermissionsPrivacyModule(context = androidContext()) + PermissionsPrivacyModuleImpl(context = androidContext()) + } + + single<TrackersServiceSupervisor> { + 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 cd85e9a..455b1a7 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 0000000..ecb24ce --- /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 <https://www.gnu.org/licenses/>. + */ + +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 80fc760..589aa74 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<ShowFeaturesWarning>(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 30c8e6b..983ba71 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 27e7fe4..9c89329 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 ed15a41..9b79dcc 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 b0c9f39..3d6ade0 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 fac1f75..ba3ba03 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -144,4 +144,9 @@ <string name="notifications_ipscrambling_title">Real IP hidden</string> <string name="notifications_ipscrambling_content">This could impact the functioning of some applications.</string> + <string name="notifications_tracker_channel_name">Tracker control flag</string> + <string name="notifications_tracker_channel_description">Highlight that the trackers are actually logged and blocked by Advanced Privacy</string> + <string name="notifications_tracker_title">Tracker control is on</string> + <string name="notifications_tracker_content">This could impact the functioning of some applications.</string> + </resources> diff --git a/build.gradle b/build.gradle index a57ee21..cd192ee 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 0000000..29721b0 --- /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 <https://www.gnu.org/licenses/>. + */ +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 0000000..6bfecbb --- /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 <https://www.gnu.org/licenses/>. + */ +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 0000000..4458e1d --- /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 <https://www.gnu.org/licenses/>. + */ +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 0000000..44b508d --- /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 <https://www.gnu.org/licenses/>. + */ +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 index 78f424b..27ba17f 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 f2e10a4..c105ceb 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 b9925d4..00bf753 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 index 59a20dd..0d32bce 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 index 95f5ff0..d31bdf4 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<ApplicationDescription> { diff --git a/settings.gradle b/settings.gradle index 0b4940e..39e58c8 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 615d310..b980706 100644 --- a/trackers/src/main/AndroidManifest.xml +++ b/trackers/src/main/AndroidManifest.xml @@ -18,21 +18,4 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="foundation.e.advancedprivacy.trackers"> - - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> - <uses-permission android:name="android.permission.WAKE_LOCK" /> - <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> - - - <application> - <service - android:name="foundation.e.advancedprivacy.trackers.services.DNSBlockerService" - android:enabled="true" - android:exported="true" /> - </application> - </manifest>
\ 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 0cfb69c..34b4e7a 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 c2c0768..64477b7 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 6aa76cf..15ff813 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<Unit>() + val newDataAvailable: SharedFlow<Unit> = _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 a7d5e49..fc57a8e 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 0000000..d9674fc --- /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 <https://www.gnu.org/licenses/>. + */ +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 0000000..e229cab --- /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 <https://www.gnu.org/licenses/>. + */ +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<DetectedTracker>() + + 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 55efeb9..e7a84b8 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<Unit>() - val newDataAvailable: SharedFlow<Unit> = _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 411b4ab..0000000 --- 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 <https://www.gnu.org/licenses/>. - */ - -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<DetectedTracker>() - - 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 55da644..fa60431 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 0000000..42afabf --- /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 0000000..f7725bf --- /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 0000000..e69de29 --- /dev/null +++ b/trackersservicee/consumer-rules.pro diff --git a/trackersservicee/proguard-rules.pro b/trackersservicee/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /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 0000000..2290432 --- /dev/null +++ b/trackersservicee/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?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 + 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/>. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="foundation.e.advancedprivacy.trackers.service"> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> + + + <application> + <service + android:name=".TrackersService" + android:enabled="true" + android:exported="true" /> + </application> +</manifest>
\ 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 index fb08910..6a2b218 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 <https://www.gnu.org/licenses/>. */ -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 index 25539e1..5f573b0 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 <https://www.gnu.org/licenses/>. */ - -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>(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 0000000..3903db4 --- /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 <https://www.gnu.org/licenses/>. + */ +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 0000000..42afabf --- /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 0000000..d5df422 --- /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 0000000..e69de29 --- /dev/null +++ b/trackersservicestandalone/consumer-rules.pro diff --git a/trackersservicestandalone/proguard-rules.pro b/trackersservicestandalone/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /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 0000000..4bfa4eb --- /dev/null +++ b/trackersservicestandalone/src/main/AndroidManifest.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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/>. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="foundation.e.advancedprivacy.trackers.service"> + + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> + <uses-permission android:name="android.permission.INTERNET" /> + + <application + android:usesCleartextTraffic="true" + > + <service android:name=".TrackersService" + android:enabled="true" + android:permission="android.permission.BIND_VPN_SERVICE" + android:exported="true" + > + <intent-filter> + <action android:name="android.net.VpnService"/> + </intent-filter> + </service> + + </application> +</manifest>
\ 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 0000000..d079e22 --- /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 <https://www.gnu.org/licenses/>. + */ +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 0000000..918977f --- /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 <https://www.gnu.org/licenses/>. + */ +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>(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 0000000..25d3e2d --- /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 <https://www.gnu.org/licenses/>. + */ +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<FeatureServiceState> = 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 0000000..7813c67 --- /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 <https://www.gnu.org/licenses/>. + */ +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 0000000..7c36ed2 --- /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 <https://www.gnu.org/licenses/>. + */ +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 0000000..d9370be --- /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 <https://www.gnu.org/licenses/>. + */ +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 0000000..ac8aee0 --- /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 <https://www.gnu.org/licenses/>. + */ +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) + } +} |