diff --git a/api/.gitignore b/api/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42afabfd2abebf31384ca7797186a27a4b7dbee8
--- /dev/null
+++ b/api/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/api/build.gradle b/api/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..b8ced30765f39c78d638ae09aec3ee3a6e78e394
--- /dev/null
+++ b/api/build.gradle
@@ -0,0 +1,90 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'maven-publish'
+
+android {
+ compileSdkVersion buildConfig.compileSdk
+
+ defaultConfig {
+ minSdkVersion buildConfig.minSdk
+ targetSdkVersion buildConfig.targetSdk
+
+ 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
+ }
+}
+
+dependencies {
+ implementation(
+ Libs.Kotlin.stdlib,
+ Libs.AndroidX.coreKtxAPI29,
+ Libs.Coroutines.core
+ )
+}
+
+//url "https://gitlab.e.foundation/api/v4/groups/e/privacy-central/-/packages/maven"
+
+publishing {
+ publications {
+ maven(MavenPublication) {
+ groupId 'foundation.e'
+ //You can either define these here or get them from project conf elsewhere
+ artifactId 'privacymodule.api'
+ version buildConfig.version.name
+ artifact "$buildDir/outputs/aar/api-release.aar"
+ //aar artifact you want to publish
+
+ //generate pom nodes for dependencies
+ pom.withXml {
+ def dependenciesNode = asNode().appendNode('dependencies')
+ configurations.implementation.allDependencies.each { dependency ->
+ if (dependency.name != 'unspecified') {
+ def dependencyNode = dependenciesNode.appendNode('dependency')
+ dependencyNode.appendNode('groupId', dependency.group)
+ dependencyNode.appendNode('artifactId', dependency.name)
+ dependencyNode.appendNode('version', dependency.version)
+ }
+ }
+ }
+ repositories {
+ def ciJobToken = System.getenv("CI_JOB_TOKEN")
+ def ciApiV4Url = System.getenv("CI_API_V4_URL")
+ if (ciJobToken != null) {
+ maven {
+ url "${ciApiV4Url}/projects/900/packages/maven"
+ credentials(HttpHeaderCredentials) {
+ name = 'Job-Token'
+ value = ciJobToken
+ }
+ authentication {
+ header(HttpHeaderAuthentication)
+ }
+ }
+ } else {
+ maven {
+ url "https://gitlab.e.foundation/api/v4/projects/900/packages/maven"
+ credentials(HttpHeaderCredentials) {
+ name = "Private-Token"
+ value = gitLabPrivateToken
+ // the variable resides in ~/.gradle/gradle.properties
+ }
+ authentication {
+ header(HttpHeaderAuthentication)
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/api/consumer-rules.pro b/api/consumer-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/api/proguard-rules.pro b/api/proguard-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce
--- /dev/null
+++ b/api/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/api/src/main/AndroidManifest.xml b/api/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..937e285dfaf5b2126d23b627ceb1d2f22a7ded9e
--- /dev/null
+++ b/api/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
\ No newline at end of file
diff --git a/api/src/main/java/foundation/e/privacymodules/DependencyInjector.kt b/api/src/main/java/foundation/e/privacymodules/DependencyInjector.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bcf82d2c511094710dbae2c58f7474741251ad82
--- /dev/null
+++ b/api/src/main/java/foundation/e/privacymodules/DependencyInjector.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules
+
+import foundation.e.privacymodules.trackers.IDNSBlocker
+
+object DependencyInjector {
+ fun initialize(
+ dnsBlocker: IDNSBlocker
+ ) {
+ this.dnsBlocker = dnsBlocker
+ }
+
+
+ lateinit var dnsBlocker: IDNSBlocker
+ private set
+}
\ No newline at end of file
diff --git a/api/src/main/java/foundation/e/privacymodules/location/IFakeLocationModule.kt b/api/src/main/java/foundation/e/privacymodules/location/IFakeLocationModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ecad2a48f937632305f1ef576b6a2a0992875eb9
--- /dev/null
+++ b/api/src/main/java/foundation/e/privacymodules/location/IFakeLocationModule.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.location
+
+/**
+ * Manage a fake location on the device.
+ */
+interface IFakeLocationModule {
+ /**
+ * Start to fake the location module. Call [setFakeLocation] after to set the fake
+ * position.
+ */
+ fun startFakeLocation()
+
+ /**
+ * Set or update the faked position.
+ * @param latitude the latitude of the fake position in degrees.
+ * @param longitude the longitude of the fake position in degrees.
+ */
+ fun setFakeLocation(latitude: Double, longitude: Double)
+
+ /**
+ * Stop the fake location module, giving back hand to the true location modules.
+ */
+ fun stopFakeLocation()
+}
diff --git a/api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt b/api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..68f7ee1d572f46e3ca212c82dbdbfa48d188664a
--- /dev/null
+++ b/api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package foundation.e.privacymodules.permissions
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PermissionInfo
+import android.content.pm.PermissionInfo.PROTECTION_DANGEROUS
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.util.Log
+import foundation.e.privacymodules.permissions.data.AppOpModes
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
+import foundation.e.privacymodules.permissions.data.PermissionDescription
+
+/**
+ * Implementation of the commons functionality between privileged and standard
+ * versions of the module.
+ * @param context an Android context, to retrieve packageManager for example.
+ */
+abstract class APermissionsPrivacyModule(protected val context: Context): IPermissionsPrivacyModule {
+
+ companion object {
+ private const val TAG = "PermissionsModule"
+ }
+ /**
+ * @see IPermissionsPrivacyModule.getAllApplications
+ */
+ override fun getAllApplications(): List {
+ val appInfos = context.packageManager.getInstalledApplications(0)
+ return appInfos.map { buildApplicationDescription(it, false) }
+ }
+
+ /**
+ * @see IPermissionsPrivacyModule.getInstalledApplications
+ */
+ override fun getInstalledApplications(): List {
+ return context.packageManager.getInstalledApplications(0)
+ .filter { it.flags and ApplicationInfo.FLAG_SYSTEM == 0 }
+ .map { buildApplicationDescription(it, false) }
+ }
+
+ /**
+ * @see IPermissionsPrivacyModule.getInstalledApplications
+ */
+ override fun getApplicationDescription(packageName: String): ApplicationDescription {
+ val appDesc = buildApplicationDescription(context.packageManager.getApplicationInfo(packageName, 0), false)
+ appDesc.icon = getApplicationIcon(appDesc.packageName)
+ return appDesc
+ }
+
+ /**
+ * * @see IPermissionsPrivacyModule.getPermissions
+ */
+ override fun getPermissions(packageName: String): List {
+ val packageInfo = context.packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
+ return packageInfo.requestedPermissions?.asList() ?: emptyList()
+ }
+
+ override fun getPermissionDescription(permissionName: String): PermissionDescription {
+ val info = context.packageManager.getPermissionInfo(permissionName, 0)
+ return PermissionDescription(
+ name = permissionName,
+ isDangerous = isPermissionsDangerous(info),
+ group = null,
+ label = info.loadLabel(context.packageManager),
+ description = info.loadDescription(context.packageManager)
+ )
+ }
+
+ /**
+ * @see IPermissionsPrivacyModule.isDangerousPermissionGranted
+ */
+ override fun isDangerousPermissionGranted(packageName: String, permissionName: String): Boolean {
+ return context.packageManager
+ .checkPermission(permissionName, packageName) == PackageManager.PERMISSION_GRANTED
+ }
+
+ // on google version, work only for the current package.
+ @Suppress("DEPRECATION")
+ override fun getAppOpMode(
+ appDesc: ApplicationDescription,
+ appOpPermissionName: String
+ ): AppOpModes {
+
+ val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
+
+ val mode = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ appOps.checkOpNoThrow(appOpPermissionName,
+
+ appDesc.uid, appDesc.packageName)
+ } else {
+ appOps.unsafeCheckOpNoThrow(
+ appOpPermissionName,
+ appDesc.uid, appDesc.packageName)
+ }
+
+ return AppOpModes.getByModeValue(mode)
+ }
+
+ override fun isPermissionsDangerous(permissionName: String): Boolean {
+ try {
+ val permissionInfo = context.packageManager.getPermissionInfo(permissionName, 0)
+ return isPermissionsDangerous(permissionInfo)
+ } catch (e: Exception) {
+ Log.w(TAG, "exception in isPermissionsDangerous(String)", e)
+ return false
+ }
+ }
+
+ @Suppress("DEPRECATION")
+ private fun isPermissionsDangerous(permissionInfo: PermissionInfo): Boolean {
+ try {
+ return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+ permissionInfo.protectionLevel and PROTECTION_DANGEROUS == 1
+ } else {
+ permissionInfo.protection == PROTECTION_DANGEROUS
+ }
+ } catch (e: Exception) {
+ Log.w(TAG, "exception in isPermissionsDangerous(PermissionInfo)", e)
+ return false
+ }
+ }
+
+ override fun buildApplicationDescription(appInfo: ApplicationInfo, withIcon: Boolean)
+ : ApplicationDescription {
+ return ApplicationDescription(
+ packageName = appInfo.packageName,
+ uid = appInfo.uid,
+ label = getAppLabel(appInfo),
+ icon = if (withIcon) getApplicationIcon(appInfo.packageName) else null
+ )
+ }
+
+ private fun getAppLabel(appInfo: ApplicationInfo): CharSequence {
+ return context.packageManager.getApplicationLabel(appInfo)
+ }
+
+ override fun getApplicationIcon(packageName: String): Drawable? {
+ return context.packageManager.getApplicationIcon(packageName)
+ }
+}
diff --git a/api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt b/api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ba85f1336c649418df0efebd6104387a204b3e83
--- /dev/null
+++ b/api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.permissions
+
+import android.content.pm.ApplicationInfo
+import android.graphics.drawable.Drawable
+import foundation.e.privacymodules.permissions.data.AppOpModes
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
+import foundation.e.privacymodules.permissions.data.PermissionDescription
+
+/**
+ * List applications and manage theirs permissions.
+ */
+interface IPermissionsPrivacyModule {
+
+ fun buildApplicationDescription(appInfo: ApplicationInfo, withIcon: Boolean = true): ApplicationDescription
+
+ /**
+ * List the installed application on the device which have not the FLAGS_SYSTEM.
+ * @return list of filled up [ApplicationDescription]
+ */
+ fun getInstalledApplications(): List
+
+ /**
+ * List all the installed application on the device.
+ * @return list of filled up [ApplicationDescription]
+ */
+ fun getAllApplications(): List
+
+ /**
+ * List of permissions names used by an app, specified by its [packageName].
+ * @param packageName the appId of the app
+ * @return the list off permission, in the "android.permission.PERMISSION" format.
+ */
+ fun getPermissions(packageName: String): List
+
+ fun getPermissionDescription(permissionName: String): PermissionDescription
+
+
+ /**
+ * Get the filled up [ApplicationDescription] for the app specified by its [packageName]
+ * @param packageName the appId of the app
+ * @return the informations about the app.
+ */
+ fun getApplicationDescription(packageName: String): ApplicationDescription
+
+ /**
+ * Check if the current runtime permission is granted for the specified app.
+ *
+ * @param packageName the packageName of the app
+ * @param permissionName the name of the permission in "android.permission.PERMISSION" format.
+ * @return the current status for this permission.
+ */
+ fun isDangerousPermissionGranted(packageName: String, permissionName: String): Boolean
+
+
+ /**
+ * Get the appOps mode for the specified [appOpPermissionName] of the specified application.
+ *
+ * @param appDesc the application
+ * @param appOpPermissionName the AppOps permission name.
+ * @return mode, as a [AppOpModes]
+ */
+ fun getAppOpMode(appDesc: ApplicationDescription, appOpPermissionName: String): AppOpModes
+
+ /**
+ * Grant or revoke the specified permission for the specigfied app.
+ * If their is not enough privileges to get the permission, return the false
+ *
+ * @param appDesc the application
+ * @param permissionName the name of the permission in "android.permission.PERMISSION" format.
+ * @param grant true grant the permission, false revoke it.
+ * @return true if the permission is or has just been granted, false if
+ * user has to do it himself.
+ */
+ fun toggleDangerousPermission(
+ appDesc: ApplicationDescription,
+ permissionName: String,
+ grant: Boolean
+ ): Boolean
+
+
+ /**
+ * Change the appOp Mode for the specified appOpPermission and application.
+ * @param appDesc the application
+ * @param appOpPermissionName the AppOps permission name.
+ * @return true if the mode has been changed, false if
+ * user has to do it himself.
+ */
+ fun setAppOpMode(
+ appDesc: ApplicationDescription,
+ appOpPermissionName: String,
+ status: AppOpModes
+ ): Boolean
+
+ /**
+ * Return true if the application is flagged Dangerous.
+ */
+ fun isPermissionsDangerous(permissionName: String): Boolean
+
+ /**
+ * Get the application icon.
+ */
+ fun getApplicationIcon(packageName: String): Drawable?
+
+ /**
+ * Authorize the specified package to be used as Vpn.
+ * @return true if authorization has been set, false if an error has occurred.
+ */
+ fun setVpnPackageAuthorization(packageName: String): Boolean
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/foundation/e/privacymodules/permissions/data/AppOpModes.kt b/api/src/main/java/foundation/e/privacymodules/permissions/data/AppOpModes.kt
new file mode 100644
index 0000000000000000000000000000000000000000..367645dd0e322c9bc4d8a0548ead10ecc687d448
--- /dev/null
+++ b/api/src/main/java/foundation/e/privacymodules/permissions/data/AppOpModes.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.permissions.data
+
+import android.app.AppOpsManager.*
+import android.os.Build
+
+enum class AppOpModes(val modeValue: Int) {
+ ALLOWED(MODE_ALLOWED),
+ IGNORED(MODE_IGNORED),
+ ERRORED(MODE_ERRORED),
+ DEFAULT(MODE_DEFAULT),
+ FOREGROUND(if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) MODE_ALLOWED else MODE_FOREGROUND);
+
+ companion object {
+ private val byMode = mapOf(
+ FOREGROUND.modeValue to FOREGROUND,
+ ALLOWED.modeValue to ALLOWED,
+ IGNORED.modeValue to IGNORED,
+ ERRORED.modeValue to ERRORED,
+ DEFAULT.modeValue to DEFAULT,
+ )
+
+ fun getByModeValue(modeValue: Int): AppOpModes {
+ return byMode.get(modeValue) ?: DEFAULT
+ }
+ }
+}
diff --git a/api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt b/api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt
new file mode 100644
index 0000000000000000000000000000000000000000..cafe256964009c51d1cf07ff72483cc41d1297c7
--- /dev/null
+++ b/api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.permissions.data
+
+import android.graphics.drawable.Drawable
+
+/**
+ * Useful informations to identify and describe an application.
+ */
+data class ApplicationDescription(
+ val packageName: String,
+ val uid: Int,
+ var label: CharSequence?,
+ var icon: Drawable?
+)
diff --git a/api/src/main/java/foundation/e/privacymodules/permissions/data/PermissionDescription.kt b/api/src/main/java/foundation/e/privacymodules/permissions/data/PermissionDescription.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9ed297d3283522a066e89461a398929e2fa405b0
--- /dev/null
+++ b/api/src/main/java/foundation/e/privacymodules/permissions/data/PermissionDescription.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.permissions.data
+
+data class PermissionDescription(
+ val name: String,
+ var isDangerous: Boolean,
+ val group: String?,
+ var label: CharSequence?,
+ var description: CharSequence?
+)
\ No newline at end of file
diff --git a/api/src/main/java/foundation/e/privacymodules/trackers/IBlockTrackersPrivacyModule.kt b/api/src/main/java/foundation/e/privacymodules/trackers/IBlockTrackersPrivacyModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..53b540e87f7bf371f6a3f6f12a40761d58d38ae6
--- /dev/null
+++ b/api/src/main/java/foundation/e/privacymodules/trackers/IBlockTrackersPrivacyModule.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.trackers
+
+
+/**
+ * Manage trackers blocking and whitelisting.
+ */
+interface IBlockTrackersPrivacyModule {
+
+
+ /**
+ * Get the state of the blockin module
+ * @return true when blocking is enabled, false otherwise.
+ */
+ fun isBlockingEnabled(): Boolean
+
+ /**
+ * Enable blocking, using the previously configured whitelists
+ */
+ fun enableBlocking()
+
+ /**
+ * Disable blocking
+ */
+ fun disableBlocking()
+
+ /**
+ * Set or unset in whitelist the App with the specified uid.
+ * @param appUid the uid of the app
+ * @param isWhiteListed true, the app will appears in whitelist, false, it won't
+ */
+ fun setWhiteListed(appUid: Int, isWhiteListed: Boolean)
+
+ /**
+ * Set or unset in whitelist the specifid tracked, for the App specified by its uid.
+ * @param tracker the tracker
+ * @param appUid the uid of the app
+ * @param isWhiteListed true, the app will appears in whitelist, false, it won't
+ */
+ fun setWhiteListed(tracker: Tracker, appUid: Int, isWhiteListed: Boolean)
+
+ /**
+ * Return true if nothing has been added to the whitelist : everything is blocked.
+ */
+ fun isWhiteListEmpty(): Boolean
+
+ /**
+ * Return the white listed App, by their UID
+ */
+ fun getWhiteListedApp(): List
+
+ /**
+ * Return true if the App is whitelisted for trackers blocking.
+ */
+ fun isWhitelisted(appUid: Int): Boolean
+
+
+ /**
+ * List the white listed trackers for an App specified by it uid
+ */
+ fun getWhiteList(appUid: Int): List
+
+ /**
+ * Callback interface to get updates about the state of the Block trackers module.
+ */
+ interface Listener {
+
+ /**
+ * Called when the trackers blocking is activated or deactivated.
+ * @param isBlocking true when activated, false otherwise.
+ */
+ fun onBlockingToggle(isBlocking: Boolean)
+ }
+
+ fun addListener(listener: Listener)
+
+ fun removeListener(listener: Listener)
+
+ fun clearListeners()
+}
diff --git a/api/src/main/java/foundation/e/privacymodules/trackers/IDNSBlocker.kt b/api/src/main/java/foundation/e/privacymodules/trackers/IDNSBlocker.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a132aefa7fc1492a639a97c07fc8e4128eae5dbd
--- /dev/null
+++ b/api/src/main/java/foundation/e/privacymodules/trackers/IDNSBlocker.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.trackers
+
+interface IDNSBlocker {
+ companion object {
+ const val DUMMY_APP_UID = -1
+ }
+
+ fun shouldBlock(hostname: String, appUid: Int): Boolean
+}
diff --git a/api/src/main/java/foundation/e/privacymodules/trackers/ITrackTrackersPrivacyModule.kt b/api/src/main/java/foundation/e/privacymodules/trackers/ITrackTrackersPrivacyModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..139290e8c974ac1de7b0140a2c25236803051c41
--- /dev/null
+++ b/api/src/main/java/foundation/e/privacymodules/trackers/ITrackTrackersPrivacyModule.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.trackers
+
+/**
+ * Get reporting about trackers calls.
+ */
+interface ITrackTrackersPrivacyModule {
+
+ fun start(trackers: List, enableNotification: Boolean = true)
+
+ /**
+ * List all the trackers encountered for a specific app.
+ */
+ fun getTrackersForApp(appUid: Int): List
+
+ /**
+ * Return the number of encountered trackers since "ever"
+ */
+ fun getTrackersCount(): Int
+
+ /**
+ * Return the number of encountere trackers since "ever", for each app uid.
+ */
+ fun getTrackersCountByApp(): Map
+
+ /**
+ * Return the number of encountered trackers for the last 24 hours
+ */
+ fun getPastDayTrackersCount(): Int
+
+ /**
+ * Return the number of encountered trackers for the last month
+ */
+ fun getPastMonthTrackersCount(): Int
+
+ /**
+ * Return the number of encountered trackers for the last year
+ */
+ fun getPastYearTrackersCount(): Int
+
+
+ /**
+ * Return number of trackers calls by hours, for the last 24hours.
+ * @return list of 24 numbers of trackers calls by hours
+ */
+ fun getPastDayTrackersCalls(): List>
+
+ /**
+ * Return number of trackers calls by day, for the last 30 days.
+ * @return list of 30 numbers of trackers calls by day
+ */
+ fun getPastMonthTrackersCalls(): List>
+
+ /**
+ * Return number of trackers calls by month, for the last 12 month.
+ * @return list of 12 numbers of trackers calls by month
+ */
+ fun getPastYearTrackersCalls(): List>
+
+ fun getPastDayTrackersCallsByApps(): Map>
+
+ fun getPastDayTrackersCallsForApp(appUId: Int): Pair
+
+ fun getPastDayMostLeakedApp(): Int
+
+ interface Listener {
+
+ /**
+ * Called when a new tracker attempt is logged. Consumer may choose to call other methods
+ * to refresh the data.
+ */
+ fun onNewData()
+ }
+
+ fun addListener(listener: Listener)
+
+ fun removeListener(listener: Listener)
+
+ fun clearListeners()
+}
\ No newline at end of file
diff --git a/api/src/main/java/foundation/e/privacymodules/trackers/Tracker.kt b/api/src/main/java/foundation/e/privacymodules/trackers/Tracker.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0a4395ad5fd12032762f087ae463bdf6b49e096d
--- /dev/null
+++ b/api/src/main/java/foundation/e/privacymodules/trackers/Tracker.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.trackers
+
+/**
+ * Describe a tracker.
+ */
+data class Tracker(
+ val id: String,
+ val hostnames: Set,
+ val label: String,
+ val exodusId: String?
+)
diff --git a/app/build.gradle b/app/build.gradle
index 5f2b3029544df83d9852c034c69497dc43d6c532..61ee623f8732de0b84823b8003468ff469dcdac1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,7 +16,10 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- manifestPlaceholders = [ persistent: "false" ]
+ manifestPlaceholders = [
+ persistent: "false",
+ mainActivityIntentFilterCategory: "android.intent.category.LAUNCHER"
+ ]
resValue("string", "mapbox_key", MAPBOX_KEY)
}
@@ -47,24 +50,36 @@ android {
dimension 'os'
minSdkVersion 29
targetSdkVersion 29
+ signingConfig signingConfigs.eDebug
}
e30 {
dimension 'os'
minSdkVersion 30
targetSdkVersion 30
+ signingConfig signingConfigs.eDebug
+ }
+ standalone {
+ dimension 'os'
+ applicationIdSuffix '.standalone'
+ minSdkVersion 26
+ targetSdkVersion 31
+ manifestPlaceholders = [
+ persistent: "false",
+ mainActivityIntentFilterCategory: "android.intent.category.LAUNCHER"
+ ]
+ signingConfig signingConfigs.debug
}
-// google {
-// applicationIdSuffix '.google'
-// dimension 'os'
-// }
}
buildTypes {
debug {
- signingConfig null // Set signing config to null as we use signingConfig per variant.
+ signingConfig null // Set signing config to null as we use signingConfig per variant.
}
release {
- manifestPlaceholders = [ persistent: "true" ]
+ manifestPlaceholders = [
+ persistent: "true",
+ mainActivityIntentFilterCategory: "android.intent.category.INFO"
+ ]
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
@@ -78,13 +93,6 @@ android {
variant.outputs.all { output ->
outputFileName = "Advanced_Privacy-${variant.versionName}-${variant.getFlavorName()}-${variant.buildType.name}.apk"
}
- if (variant.buildType.name == "debug") {
- if (variant.getFlavorName() == "e29" || variant.getFlavorName() == "e30") {
- variant.mergedFlavor.signingConfig = signingConfigs.eDebug
- } else {
- variant.mergedFlavor.signingConfig = signingConfigs.debug
- }
- }
}
compileOptions {
@@ -103,53 +111,51 @@ android {
}
dependencies {
+ implementation project(':api')
- compileOnly files('libs/e-ui-sdk-1.0.1-q.jar')
- implementation files('libs/lineage-sdk.jar')
- // include the google specific version of the modules, just for the google flavor
- //googleImplementation project(":privacymodulesgoogle")
- // include the e specific version of the modules, just for the e flavor
-
- implementation 'foundation.e:privacymodule.trackerfilter:0.7.0'
- implementation 'foundation.e:privacymodule.api:1.1.0'
- e29Implementation 'foundation.e:privacymodule.e-29:0.4.3'
- e30Implementation 'foundation.e:privacymodule.e-30:0.4.3'
- implementation 'foundation.e:privacymodule.tor:0.2.4'
-
+ standaloneImplementation project(':permissionsstandalone')
+ e29Implementation('foundation.e:privacymodule.e-29:1.2.0') {
+ exclude group: 'foundation.e', module: 'privacymodule.api'
+ }
+ e30Implementation('foundation.e:privacymodule.e-30:1.2.0') {
+ exclude group: 'foundation.e', module: 'privacymodule.api'
+ }
- // implementation Libs.Kotlin.stdlib
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$Versions.kotlin"
-// implementation Libs.AndroidX.coreKtx
- implementation "androidx.core:core-ktx:1.8.0"
-
-// implementation Libs.AndroidX.Fragment.fragmentKtx
- implementation "androidx.fragment:fragment-ktx:$Versions.fragment"
+ implementation project(':fakelocation')
- implementation 'androidx.appcompat:appcompat:1.4.2'
-// implementation Libs.AndroidX.Lifecycle.runtime
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:$Versions.lifecycle"
-// implementation Libs.AndroidX.Lifecycle.viewmodel
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$Versions.lifecycle"
+ e29CompileOnly files('libs/e-ui-sdk-1.0.1-q.jar')
+ e29Implementation files('libs/lineage-sdk.jar')
- implementation 'androidx.work:work-runtime-ktx:2.7.1'
+ e30CompileOnly files('libs/e-ui-sdk-1.0.1-q.jar')
+ e30Implementation files('libs/lineage-sdk.jar')
- implementation 'com.google.android.material:material:1.6.1'
+ implementation project(':trackers')
+ implementation 'foundation.e:privacymodule.tor:0.2.4'
- implementation 'com.squareup.retrofit2:retrofit:2.9.0'
- implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
+ implementation (
+ Libs.Kotlin.stdlib,
+ Libs.AndroidX.coreKtx,
+ Libs.AndroidX.appCompat,
+ Libs.AndroidX.Fragment.fragmentKtx,
+ Libs.AndroidX.Lifecycle.runtime,
+ Libs.AndroidX.Lifecycle.viewmodel,
+ Libs.AndroidX.work,
+ Libs.material,
-// implementation Libs.MapBox.sdk
- implementation "com.mapbox.mapboxsdk:mapbox-android-sdk:$Versions.mapbox"
- implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
+ Libs.Retrofit.retrofit,
+ Libs.Retrofit.scalars,
+ Libs.MapBox.sdk,
+ Libs.mpAndroidCharts
+ )
+ debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
- debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}
static def log(Object val) {
diff --git a/app/src/google/res/values/strings.xml b/app/src/google/res/values/strings.xml
deleted file mode 100644
index ebf51d065e4e754c9819409f4e3ebc446cc04484..0000000000000000000000000000000000000000
--- a/app/src/google/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- google - PrivacyModulesDemo
-
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d285b6f8f473a4e765eb78e2f06d47d13c6359b5..d2a824a13d4b30c53cbd4816e634a3e98664fa33 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,4 +1,20 @@
+
@@ -53,19 +70,28 @@
android:resource="@xml/widget_info"
/>
-
+
+ android:launchMode="singleTask"
+ android:exported="true"
+ >
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
index 6be3724b7bc61a59ab6f4ce8b0d9e5d81fb6063b..a44a00acf84db9472f020188d5cbeeec1b958abf 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
@@ -40,16 +40,15 @@ import foundation.e.privacycentralapp.features.location.FakeLocationViewModel
import foundation.e.privacycentralapp.features.trackers.TrackersViewModel
import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersFragment
import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersViewModel
+import foundation.e.privacymodules.fakelocation.FakeLocationModule
import foundation.e.privacymodules.ipscrambler.IpScramblerModule
import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
-import foundation.e.privacymodules.location.FakeLocationModule
import foundation.e.privacymodules.location.IFakeLocationModule
import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
import foundation.e.privacymodules.permissions.data.ApplicationDescription
import foundation.e.privacymodules.trackers.api.BlockTrackersPrivacyModule
import foundation.e.privacymodules.trackers.api.TrackTrackersPrivacyModule
import kotlinx.coroutines.DelicateCoroutinesApi
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.GlobalScope
/**
@@ -61,7 +60,7 @@ class DependencyContainer(val app: Application) {
val context: Context by lazy { app.applicationContext }
// Drivers
- private val fakeLocationModule: IFakeLocationModule by lazy { FakeLocationModule(app.applicationContext) }
+ private val fakeLocationModule: FakeLocationModule by lazy { FakeLocationModule(app.applicationContext) }
private val permissionsModule by lazy { PermissionsPrivacyModule(app.applicationContext) }
private val ipScramblerModule: IIpScramblerModule by lazy { IpScramblerModule(app.applicationContext) }
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt
index aa4276d8d3db8ce20e780f9516c4c06fddac6694..f7b54393bd115bd00a3e4628397640e04b1ac13a 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt
@@ -28,7 +28,7 @@ import android.util.Log
import foundation.e.privacycentralapp.data.repositories.LocalStateRepository
import foundation.e.privacycentralapp.domain.entities.LocationMode
import foundation.e.privacycentralapp.dummy.CityDataSource
-import foundation.e.privacymodules.location.IFakeLocationModule
+import foundation.e.privacymodules.fakelocation.FakeLocationModule
import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
import foundation.e.privacymodules.permissions.data.AppOpModes
import foundation.e.privacymodules.permissions.data.ApplicationDescription
@@ -39,7 +39,7 @@ import kotlinx.coroutines.launch
import kotlin.random.Random
class FakeLocationStateUseCase(
- private val fakeLocationModule: IFakeLocationModule,
+ private val fakeLocationModule: FakeLocationModule,
private val permissionsModule: PermissionsPrivacyModule,
private val localStateRepository: LocalStateRepository,
private val citiesRepository: CityDataSource,
@@ -61,23 +61,15 @@ class FakeLocationStateUseCase(
private val locationManager: LocationManager
get() = appContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
- private fun acquireLocationPermission() {
- if (appContext.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
- permissionsModule.toggleDangerousPermission(
- appDesc,
- android.Manifest.permission.ACCESS_FINE_LOCATION,
- true
- )
- }
+ private fun hasAcquireLocationPermission(): Boolean {
+ return (appContext.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
+ || permissionsModule.toggleDangerousPermission(appDesc, android.Manifest.permission.ACCESS_FINE_LOCATION, true)
}
private fun applySettings(isQuickPrivacyEnabled: Boolean, fakeLocation: Pair?, isSpecificLocation: Boolean = false) {
_configuredLocationMode.value = computeLocationMode(fakeLocation, isSpecificLocation)
- if (isQuickPrivacyEnabled && fakeLocation != null) {
- if (permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) != AppOpModes.ALLOWED) {
- permissionsModule.setAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpModes.ALLOWED)
- }
+ if (isQuickPrivacyEnabled && fakeLocation != null && hasAcquireMockLocationPermission()) {
fakeLocationModule.startFakeLocation()
fakeLocationModule.setFakeLocation(fakeLocation.first.toDouble(), fakeLocation.second.toDouble())
localStateRepository.locationMode.value = configuredLocationMode.value.first
@@ -87,6 +79,11 @@ class FakeLocationStateUseCase(
}
}
+ private fun hasAcquireMockLocationPermission(): Boolean {
+ return (permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) == AppOpModes.ALLOWED)
+ || permissionsModule.setAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpModes.ALLOWED)
+ }
+
fun setSpecificLocation(latitude: Float, longitude: Float) {
if (!localStateRepository.isQuickPrivacyEnabled) {
localStateRepository.setShowQuickPrivacyDisabledMessage(true)
@@ -161,8 +158,11 @@ class FakeLocationStateUseCase(
}
}
- fun startListeningLocation() {
- requestLocationUpdates(localListener)
+ fun startListeningLocation(): Boolean {
+ return if (hasAcquireLocationPermission()) {
+ requestLocationUpdates(localListener)
+ true
+ } else false
}
fun stopListeningLocation() {
@@ -170,7 +170,6 @@ class FakeLocationStateUseCase(
}
fun requestLocationUpdates(listener: LocationListener) {
- acquireLocationPermission()
try {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, // TODO: tight this with fakelocation module.
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
index 2b858e9540a7fae1ccef41120c03199e173bb4ec..d98cb5db8cc2e2dfd29d7b86826fa72d6ce7e50a 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
@@ -17,6 +17,7 @@
package foundation.e.privacycentralapp.features.location
+import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.location.Location
@@ -24,6 +25,7 @@ import android.os.Bundle
import android.text.Editable
import android.view.View
import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.NonNull
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
@@ -81,6 +83,16 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
private var inputJob: Job? = null
+ private val locationPermissionRequest = registerForActivityResult(
+ ActivityResultContracts.RequestMultiplePermissions()
+ ) { permissions ->
+ if (permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false)
+ || permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false)
+ ) {
+ viewModel.submitAction(Action.StartListeningLocation)
+ } // TODO: else.
+ }
+
companion object {
private const val DEBOUNCE_PERIOD = 1000L
}
@@ -147,6 +159,13 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
is FakeLocationViewModel.SingleEvent.LocationUpdatedEvent -> {
updateLocation(event.location, event.mode)
}
+ is FakeLocationViewModel.SingleEvent.RequestLocationPermission -> {
+ // TODO for standalone: rationale dialog
+ locationPermissionRequest.launch(arrayOf(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ))
+ }
}
}
}
@@ -326,13 +345,13 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
override fun onResume() {
super.onResume()
- viewModel.submitAction(Action.EnterScreen)
+ viewModel.submitAction(Action.StartListeningLocation)
binding.mapView.onResume()
}
override fun onPause() {
super.onPause()
- viewModel.submitAction(Action.LeaveScreen)
+ viewModel.submitAction(Action.StopListeningLocation)
binding.mapView.onPause()
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt
index af20a7298335a195988011f2300bc513ab663adb..afba3d0f6429b57eaf8b6abadae9c0ee74eabebe 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt
@@ -86,8 +86,8 @@ class FakeLocationViewModel(
fun submitAction(action: Action) = viewModelScope.launch {
when (action) {
- is Action.EnterScreen -> fakeLocationStateUseCase.startListeningLocation()
- is Action.LeaveScreen -> fakeLocationStateUseCase.stopListeningLocation()
+ is Action.StartListeningLocation -> actionStartListeningLocation()
+ is Action.StopListeningLocation -> fakeLocationStateUseCase.stopListeningLocation()
is Action.SetSpecificLocationAction -> setSpecificLocation(action)
is Action.UseRandomLocationAction -> fakeLocationStateUseCase.setRandomLocation()
is Action.UseRealLocationAction ->
@@ -97,18 +97,26 @@ class FakeLocationViewModel(
}
}
+ private suspend fun actionStartListeningLocation() {
+ val started = fakeLocationStateUseCase.startListeningLocation()
+ if (!started) {
+ _singleEvents.emit(SingleEvent.RequestLocationPermission)
+ }
+ }
+
private suspend fun setSpecificLocation(action: Action.SetSpecificLocationAction) {
specificLocationInputFlow.emit(action)
}
sealed class SingleEvent {
data class LocationUpdatedEvent(val mode: LocationMode, val location: Location?) : SingleEvent()
+ object RequestLocationPermission: SingleEvent()
data class ErrorEvent(val error: String) : SingleEvent()
}
sealed class Action {
- object EnterScreen : Action()
- object LeaveScreen : Action()
+ object StartListeningLocation : Action()
+ object StopListeningLocation : Action()
object UseRealLocationAction : Action()
object UseRandomLocationAction : Action()
data class SetSpecificLocationAction(
diff --git a/app/src/standalone/res/values-night/colors.xml b/app/src/standalone/res/values-night/colors.xml
new file mode 100644
index 0000000000000000000000000000000000000000..079b968ba33a68bb7a89808dc24c60abffaf17b3
--- /dev/null
+++ b/app/src/standalone/res/values-night/colors.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ #272727
+ #5DB2FF
+
+ #CCFFFFFF
+ #8CFFFFFF
+
+ #121212
+
+
\ No newline at end of file
diff --git a/app/src/standalone/res/values/colors.xml b/app/src/standalone/res/values/colors.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bd2792207d83028316a6690e71f7fc6bf6f41618
--- /dev/null
+++ b/app/src/standalone/res/values/colors.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ #FFFFFF
+ #0086FF
+
+ #CC000000
+ #8C000000
+
+ #FAFAFA
+
\ No newline at end of file
diff --git a/app/src/standalone/res/values/strings.xml b/app/src/standalone/res/values/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7719e7b9a59b85e0b4951162dd050e0e2421a480
--- /dev/null
+++ b/app/src/standalone/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ A-P - Standalone
+
diff --git a/build.gradle b/build.gradle
index 2442f0188f2f2dac62086350bef63d721c83cc61..5222057fed9e3b54bf5776daa3e04ac42d853d4d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,11 +6,11 @@ buildscript {
ext.buildConfig = [
'compileSdk': 31,
'minSdk' : 26,
- 'targetSdk' : 30,
+ 'targetSdk' : 31,
'version' : [
'major': 1,
- 'minor': 1,
- 'patch': 2,
+ 'minor': 2,
+ 'patch': 0,
],
]
@@ -31,7 +31,7 @@ buildscript {
dependencies {
classpath Libs.androidGradlePlugin
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
+ classpath Libs.Kotlin.gradlePlugin
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -41,6 +41,7 @@ buildscript {
plugins {
id 'com.diffplug.spotless' version '5.12.4'
id 'com.github.ben-manes.versions' version '0.38.0'
+ id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
allprojects {
@@ -49,7 +50,6 @@ allprojects {
kotlinOptions {
freeCompilerArgs = ['-Xjvm-default=enable', '-opt-in=kotlin.RequiresOptIn']
-
jvmTarget = "1.8"
}
}
diff --git a/dependencies.gradle b/dependencies.gradle
index dcb9f9de6e29034972734aa71e7cfcd5becf548e..ed329c786658529c5c56b0e8dd4831edf451b6ac 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -6,7 +6,7 @@ def versions = [
]
ext.Versions = versions
-libs.androidGradlePlugin = "com.android.tools.build:gradle:4.1.3"
+libs.androidGradlePlugin = "com.android.tools.build:gradle:7.2.1"
libs.timber = "com.jakewharton.timber:timber:4.7.1"
@@ -25,7 +25,9 @@ libs.Kotlin = [
gradlePlugin: "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin",
]
-versions.coroutines = "1.4.2"
+
+
+versions.coroutines = "1.6.1"
libs.Coroutines = [
core: "org.jetbrains.kotlinx:kotlinx-coroutines-core:$versions.coroutines",
android: "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.coroutines",
@@ -36,7 +38,10 @@ libs.AndroidX = [
collection: "androidx.collection:collection-ktx:1.1.0",
palette: "androidx.palette:palette:1.0.0",
archCoreTesting: "androidx.arch.core:core-testing:2.1.0",
- coreKtx: "androidx.core:core-ktx:1.5.0-beta01",
+ coreKtx: "androidx.core:core-ktx:1.8.0",
+ coreKtxAPI29: "androidx.core:core-ktx:1.6.0",
+ appCompat: 'androidx.appcompat:appcompat:1.4.2',
+ work: 'androidx.work:work-runtime-ktx:2.7.1',
]
versions.fragment = "1.5.0"
@@ -82,7 +87,17 @@ libs.Hilt = [
gradlePlugin: "com.google.dagger:hilt-android-gradle-plugin:$versions.hilt",
]
+libs.material = 'com.google.android.material:material:1.6.1'
+
+libs.Retrofit = [
+ retrofit: 'com.squareup.retrofit2:retrofit:2.9.0',
+ scalars: 'com.squareup.retrofit2:converter-scalars:2.9.0'
+]
+
+
versions.mapbox="9.6.1"
libs.MapBox = [
sdk: "com.mapbox.mapboxsdk:mapbox-android-sdk:$versions.mapbox"
]
+
+libs.mpAndroidCharts = 'com.github.PhilJay:MPAndroidChart:v3.1.0'
diff --git a/fakelocation/.gitignore b/fakelocation/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42afabfd2abebf31384ca7797186a27a4b7dbee8
--- /dev/null
+++ b/fakelocation/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/fakelocation/build.gradle b/fakelocation/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..ea28e4493de545156c50f07e2963846ce61ec0d2
--- /dev/null
+++ b/fakelocation/build.gradle
@@ -0,0 +1,54 @@
+/*
+ * 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 .
+ */
+
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdkVersion buildConfig.compileSdk
+
+ defaultConfig {
+ minSdkVersion buildConfig.minSdk
+ targetSdkVersion buildConfig.targetSdk
+
+ 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 (
+ Libs.Kotlin.stdlib,
+ Libs.AndroidX.coreKtx,
+ Libs.Coroutines.core
+ )
+}
diff --git a/fakelocation/consumer-rules.pro b/fakelocation/consumer-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/fakelocation/fakelocationdemo/.gitignore b/fakelocation/fakelocationdemo/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42afabfd2abebf31384ca7797186a27a4b7dbee8
--- /dev/null
+++ b/fakelocation/fakelocationdemo/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/fakelocation/fakelocationdemo/build.gradle b/fakelocation/fakelocationdemo/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..12ed2e797bb5156c00e7c384de7dbc771f7f08a4
--- /dev/null
+++ b/fakelocation/fakelocationdemo/build.gradle
@@ -0,0 +1,71 @@
+/*
+ * 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 .
+ */
+
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdkVersion buildConfig.compileSdk
+
+ defaultConfig {
+ applicationId "foundation.e.privacymodules.fakelocationdemo"
+ minSdkVersion buildConfig.minSdk
+ targetSdkVersion buildConfig.targetSdk
+
+ versionCode buildConfig.version.code
+ versionName buildConfig.version.name
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ 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'
+ allWarningsAsErrors = false
+ }
+ buildFeatures {
+ dataBinding true
+ }
+}
+
+dependencies {
+ implementation project(':api')
+ implementation project(':fakelocation')
+ implementation project(':permissionsstandalone')
+
+
+ implementation (
+ Libs.Kotlin.stdlib,
+ Libs.AndroidX.coreKtx,
+ Libs.AndroidX.appCompat,
+ Libs.material,
+ )
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
\ No newline at end of file
diff --git a/fakelocation/fakelocationdemo/proguard-rules.pro b/fakelocation/fakelocationdemo/proguard-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce
--- /dev/null
+++ b/fakelocation/fakelocationdemo/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/fakelocation/fakelocationdemo/src/main/AndroidManifest.xml b/fakelocation/fakelocationdemo/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..202599a328d91031dcb428cc20bf7d417a0d3cbd
--- /dev/null
+++ b/fakelocation/fakelocationdemo/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 0000000000000000000000000000000000000000..1b0a35bef7ab10e3d2c178c56e4e20f4934526a7
--- /dev/null
+++ b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.fakelocationdemo
+
+import android.Manifest
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.PackageManager
+import android.location.Location
+import android.location.LocationListener
+import android.location.LocationManager
+import android.os.Bundle
+import android.os.Process.myUid
+import android.util.Log
+import android.view.View
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import androidx.databinding.DataBindingUtil
+import foundation.e.privacymodules.fakelocation.FakeLocationModule
+import foundation.e.privacymodules.fakelocationdemo.databinding.ActivityMainBinding
+import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
+import foundation.e.privacymodules.permissions.data.AppOpModes
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
+
+class MainActivity : AppCompatActivity() {
+ companion object {
+ const val TAG = "fakeLoc"
+ }
+
+ private val fakeLocationModule: FakeLocationModule by lazy { FakeLocationModule(this) }
+ private val permissionsModule by lazy { PermissionsPrivacyModule(this) }
+
+ private lateinit var binding: ActivityMainBinding
+
+ private val appDesc by lazy {
+ ApplicationDescription(
+ packageName = packageName,
+ uid = myUid(),
+ label = getString(R.string.app_name),
+ icon = null
+ )
+ }
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
+
+ actionBar?.setDisplayHomeAsUpEnabled(true)
+
+ binding.view = this
+ }
+
+ override fun onResume() {
+ super.onResume()
+ updateData("")
+ }
+
+ private fun updateData(mockedLocation: String) {
+ binding.granted = permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) == AppOpModes.ALLOWED
+
+ binding.mockedLocation = mockedLocation
+ }
+
+ private val listener = object: LocationListener {
+ override fun onLocationChanged(location: Location) {
+ binding.currentLocation = "lat: ${location.latitude} - lon: ${location.longitude}"
+ }
+
+ override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onProviderEnabled(provider: String) {
+ binding.providerInfo = "onProdivderEnabled: $provider"
+ }
+
+ override fun onProviderDisabled(provider: String) {
+ binding.providerInfo = "onProdivderDisabled: $provider"
+ }
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun onClickToggleListenLocation(view: View?) {
+ val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
+
+ if (binding.toggleListenLocation.isChecked) {
+ requireLocationPermissions()
+ return
+ }
+
+ locationManager.removeUpdates(listener)
+ binding.currentLocation = "no listening"
+ }
+
+ private fun startLocationUpdates() {
+ val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
+
+ try {
+ Log.d(TAG, "requestLocationUpdates")
+ locationManager.requestLocationUpdates(
+ LocationManager.GPS_PROVIDER, // TODO: tight this with fakelocation module.
+ 1000L,
+ 1f,
+ listener
+ )
+ binding.currentLocation = "listening started"
+ } catch (se: SecurityException) {
+ Log.e(TAG, "Missing permission", se)
+ }
+ }
+
+ private val locationPermissionRequest = registerForActivityResult(
+ ActivityResultContracts.RequestMultiplePermissions()
+ ) { permissions ->
+ if (permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false)
+ || permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false)
+ ) {
+ startLocationUpdates()
+ }
+ }
+
+ private fun requireLocationPermissions() {
+ if (ContextCompat.checkSelfPermission(this,
+ Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ startLocationUpdates()
+ } else {
+ // Before you perform the actual permission request, check whether your app
+ // already has the permissions, and whether your app needs to show a permission
+ // rationale dialog. For more details, see Request permissions.
+ locationPermissionRequest.launch(
+ arrayOf(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ )
+ )
+ }
+
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun onClickPermission(view: View?) {
+ val isGranted = permissionsModule.setAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION,
+ AppOpModes.ALLOWED)
+
+ if (isGranted) {
+ updateData("")
+ return
+ }
+ //dev mode disabled
+ val alertDialog = AlertDialog.Builder(this)
+ alertDialog
+ .setTitle("Mock location disabled")
+ .setNegativeButton("Cancel") { _, _ ->
+ }
+ alertDialog.create().show()
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun onClickReset(view: View?) {
+ try {
+ fakeLocationModule.stopFakeLocation()
+ } catch(e: Exception) {
+ Log.e(TAG, "Can't stop FakeLocation", e)
+ }
+ }
+
+ private fun setFakeLocation(latitude: Double, longitude: Double) {
+ try {
+ fakeLocationModule.startFakeLocation()
+ } catch(e: Exception) {
+ Log.e(TAG, "Can't startFakeLocation", e)
+ }
+ fakeLocationModule.setFakeLocation(latitude, longitude)
+ updateData("lat: ${latitude} - lon: ${longitude}")
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun onClickParis(view: View?) {
+ setFakeLocation(48.8502282, 2.3542286)
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun onClickLondon(view: View?) {
+ setFakeLocation(51.5287718, -0.2416803)
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun onClickAmsterdam(view: View?) {
+ setFakeLocation(52.3547498, 4.8339211)
+ }
+}
diff --git a/fakelocation/fakelocationdemo/src/main/res/layout/activity_main.xml b/fakelocation/fakelocationdemo/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000000000000000000000000000000000000..33fce699a4bc61e33b29f66650f19e19d290445c
--- /dev/null
+++ b/fakelocation/fakelocationdemo/src/main/res/layout/activity_main.xml
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fakelocation/fakelocationdemo/src/main/res/values/colors.xml b/fakelocation/fakelocationdemo/src/main/res/values/colors.xml
new file mode 100644
index 0000000000000000000000000000000000000000..29591a809c0901fa4811b600d0e5e77acd0c07b6
--- /dev/null
+++ b/fakelocation/fakelocationdemo/src/main/res/values/colors.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/fakelocation/fakelocationdemo/src/main/res/values/strings.xml b/fakelocation/fakelocationdemo/src/main/res/values/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..17b69db9ebc45090e2725a45ec01fa9a06ce89c6
--- /dev/null
+++ b/fakelocation/fakelocationdemo/src/main/res/values/strings.xml
@@ -0,0 +1,20 @@
+
+
+
+ FakeLocationDemo
+
\ No newline at end of file
diff --git a/fakelocation/fakelocationdemo/src/main/res/values/themes.xml b/fakelocation/fakelocationdemo/src/main/res/values/themes.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9ce6d2872267c65b3d756b5f48b41b83c90a161c
--- /dev/null
+++ b/fakelocation/fakelocationdemo/src/main/res/values/themes.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fakelocation/proguard-rules.pro b/fakelocation/proguard-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce
--- /dev/null
+++ b/fakelocation/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/fakelocation/src/main/AndroidManifest.xml b/fakelocation/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5077c24daf19d0d51fcfdbbcb87538db384245cd
--- /dev/null
+++ b/fakelocation/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/FakeLocationModule.kt b/fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/FakeLocationModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..43a454592e787c20f14708d5e06ee4db986ea3e9
--- /dev/null
+++ b/fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/FakeLocationModule.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.fakelocation
+
+import android.content.Context
+import android.content.Context.LOCATION_SERVICE
+import android.location.Criteria
+import android.location.Location
+import android.location.LocationManager
+import android.os.Build
+import android.os.SystemClock
+import android.util.Log
+
+/**
+ * Implementation of the functionality of fake location.
+ * All of them are available for normal application, so just one version is enough.
+ *
+ * @param context an Android context, to retrieve system services for example.
+ */
+class FakeLocationModule(protected val context: Context) {
+
+ /**
+ * List of all the Location provider that will be mocked.
+ */
+ private val providers = listOf(LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER)
+
+ /**
+ * Handy accessor to the locationManager service.
+ * We avoid getting it on module initialization to wait for the context to be ready.
+ */
+ private val locationManager: LocationManager get() =
+ context.getSystemService(LOCATION_SERVICE) as LocationManager
+
+ /**
+ * @see IFakeLocationModule.startFakeLocation
+ */
+ @Synchronized
+ fun startFakeLocation() {
+ providers.forEach { provider ->
+ try {
+ locationManager.removeTestProvider(provider)
+ } catch(e: Exception) {
+ Log.d("FakeLocationModule", "Test provider $provider already removed.")
+ }
+
+ locationManager.addTestProvider(
+ provider,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true,
+ true,
+ Criteria.POWER_LOW, Criteria.ACCURACY_FINE)
+ locationManager.setTestProviderEnabled(provider, true)
+ }
+ }
+
+ fun setFakeLocation(latitude: Double, longitude: Double) {
+ context.startService(FakeLocationService.buildFakeLocationIntent(context, latitude, longitude))
+ }
+
+ internal fun setTestProviderLocation(latitude: Double, longitude: Double) {
+ providers.forEach { provider ->
+ val location = Location(provider)
+ location.latitude = latitude
+ location.longitude = longitude
+
+ // Set default value for all the other required fields.
+ location.altitude = 3.0
+ location.time = System.currentTimeMillis()
+ location.speed = 0.01f
+ location.bearing = 1f
+ location.accuracy = 3f
+ location.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ location.bearingAccuracyDegrees = 0.1f
+ location.verticalAccuracyMeters = 0.1f
+ location.speedAccuracyMetersPerSecond = 0.01f
+ }
+
+ locationManager.setTestProviderLocation(provider, location)
+ }
+ }
+
+ /**
+ * @see IFakeLocationModule.stopFakeLocation
+ */
+ fun stopFakeLocation() {
+ context.stopService(FakeLocationService.buildStopIntent(context))
+ providers.forEach { provider ->
+ try {
+ locationManager.setTestProviderEnabled(provider, false)
+ locationManager.removeTestProvider(provider)
+ } catch (e: Exception) {
+ Log.d("FakeLocationModule", "Test provider $provider already removed.")
+ }
+ }
+ }
+}
diff --git a/fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/FakeLocationService.kt b/fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/FakeLocationService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1337ddd0f741d74e0873b9dbfc32973aea91392a
--- /dev/null
+++ b/fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/FakeLocationService.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.fakelocation
+
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.os.CountDownTimer
+import android.os.IBinder
+import android.util.Log
+
+class FakeLocationService: Service() {
+
+ enum class Actions {
+ START_FAKE_LOCATION
+ }
+
+ companion object {
+ private const val PERIOD_LOCATION_UPDATE = 1000L
+ private const val PERIOD_UPDATES_SERIE = 2 * 60 * 1000L
+
+ private const val PARAM_LATITUDE = "PARAM_LATITUDE"
+ private const val PARAM_LONGITUDE = "PARAM_LONGITUDE"
+
+ fun buildFakeLocationIntent(context: Context, latitude: Double, longitude: Double): Intent {
+ return Intent(context, FakeLocationService::class.java).apply {
+ action = Actions.START_FAKE_LOCATION.name
+ putExtra(PARAM_LATITUDE, latitude)
+ putExtra(PARAM_LONGITUDE, longitude)
+ }
+ }
+
+ fun buildStopIntent(context: Context) = Intent(context, FakeLocationService::class.java)
+ }
+
+ private lateinit var fakeLocationModule: FakeLocationModule
+
+ private var countDownTimer: CountDownTimer? = null
+
+ private var fakeLocation: Pair? = null
+
+ override fun onCreate() {
+ super.onCreate()
+ fakeLocationModule = FakeLocationModule(applicationContext)
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ intent?.let {
+ when (it.action?.let { str -> Actions.valueOf(str) }) {
+ Actions.START_FAKE_LOCATION -> {
+
+ fakeLocation = Pair(
+ it.getDoubleExtra(PARAM_LATITUDE, 0.0),
+ it.getDoubleExtra(PARAM_LONGITUDE, 0.0)
+ )
+ initTimer()
+ }
+ else -> {}
+ }
+ }
+
+ return START_STICKY
+ }
+
+ override fun onDestroy() {
+ countDownTimer?.cancel()
+ super.onDestroy()
+ }
+
+
+ private fun initTimer() {
+ countDownTimer?.cancel()
+ countDownTimer = object: CountDownTimer(PERIOD_UPDATES_SERIE, PERIOD_LOCATION_UPDATE) {
+ override fun onTick(millisUntilFinished: Long) {
+ fakeLocation?.let {
+ try {
+ fakeLocationModule.setTestProviderLocation(
+ it.first,
+ it.second
+ )
+ } catch (e: Exception) {
+ Log.d("FakeLocationService", "setting fake location", e)
+ }
+ }
+ }
+
+ override fun onFinish() {
+ initTimer()
+ }
+ }.start()
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return null
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index dc62e2d4c7735b3e7ce190c94e9755aef50f4634..35be582fc9170d7b365c99f974331224dff38557 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Apr 27 19:00:30 IST 2021
+#Sat Jul 16 09:07:59 CEST 2022
distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
+zipStoreBase=GRADLE_USER_HOME
diff --git a/permissionsstandalone/.gitignore b/permissionsstandalone/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42afabfd2abebf31384ca7797186a27a4b7dbee8
--- /dev/null
+++ b/permissionsstandalone/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/permissionsstandalone/build.gradle b/permissionsstandalone/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..aadb84f8149dd0b49a9dab3ac4c6b31c7a537417
--- /dev/null
+++ b/permissionsstandalone/build.gradle
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdkVersion buildConfig.compileSdk
+
+ defaultConfig {
+ minSdkVersion buildConfig.minSdk
+ targetSdkVersion buildConfig.targetSdk
+
+ 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(
+ Libs.Kotlin.stdlib,
+ Libs.AndroidX.coreKtx,
+ Libs.Coroutines.core
+ )
+
+ implementation project(':api')
+
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
\ No newline at end of file
diff --git a/permissionsstandalone/consumer-rules.pro b/permissionsstandalone/consumer-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/permissionsstandalone/proguard-rules.pro b/permissionsstandalone/proguard-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce
--- /dev/null
+++ b/permissionsstandalone/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/permissionsstandalone/src/main/AndroidManifest.xml b/permissionsstandalone/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..662ea44a2a03aaba0defbc6b45ec8ee5de6f0216
--- /dev/null
+++ b/permissionsstandalone/src/main/AndroidManifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt b/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d32cadad706526569e3c5689533d5dc1a39ae1f1
--- /dev/null
+++ b/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.privacymodules.permissions
+
+import android.content.Context
+import android.os.Build
+import android.provider.Settings
+import foundation.e.privacymodules.permissions.data.AppOpModes
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
+
+/**
+ * Implements [IPermissionsPrivacyModule] using only API authorized on the PlayStore.
+ */
+class PermissionsPrivacyModule(context: Context): APermissionsPrivacyModule(context) {
+ /**
+ * @see IPermissionsPrivacyModule.toggleDangerousPermission
+ * Return an ManualAction to go toggle manually the permission in the ap page of the settings.
+ */
+ override fun toggleDangerousPermission(
+ appDesc: ApplicationDescription,
+ permissionName: String,
+ grant: Boolean): Boolean = false
+
+
+ override fun setAppOpMode(
+ appDesc: ApplicationDescription,
+ appOpPermissionName: String,
+ status: AppOpModes
+ ): Boolean = false
+
+ override fun setVpnPackageAuthorization(packageName: String): Boolean {
+ return false
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index e39b561bc2e087317561b52745c08b73d3c6aa85..24b6eefdf6bd5dac2ce8125b101501452d1a2666 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,7 @@
include ':app'
-rootProject.name = "PrivacyCentralApp"
\ No newline at end of file
+rootProject.name = "PrivacyCentralApp"
+include ':fakelocation'
+include ':fakelocation:fakelocationdemo'
+include ':api'
+include ':permissionsstandalone'
+include ':trackers'
diff --git a/trackers/build.gradle b/trackers/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..51f8448b91b13398bac27c9e63d094d1a629a079
--- /dev/null
+++ b/trackers/build.gradle
@@ -0,0 +1,45 @@
+/*
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion buildConfig.compileSdk
+
+ defaultConfig {
+ minSdkVersion buildConfig.minSdk
+ targetSdkVersion buildConfig.targetSdk
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+dependencies{
+ implementation project(":api")
+}
diff --git a/trackers/src/main/AndroidManifest.xml b/trackers/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..debdf61bcc26cefa515bf3d62609c1af1cec713c
--- /dev/null
+++ b/trackers/src/main/AndroidManifest.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.java
new file mode 100644
index 0000000000000000000000000000000000000000..80f00c1314c47a867b976f6ba995bb271cf4a98d
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.java
@@ -0,0 +1,173 @@
+/*
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+/*
+ PersonalDNSFilter 1.5
+ Copyright (C) 2017 Ingo Zenz
+ Copyright (C) 2021 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+package foundation.e.privacymodules.trackers;
+
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import foundation.e.privacymodules.trackers.data.TrackersRepository;
+import foundation.e.privacymodules.trackers.data.WhitelistRepository;
+
+
+public class DNSBlockerRunnable implements Runnable {
+
+ LocalServerSocket resolverReceiver;
+ boolean stopped = false;
+ private final TrackersLogger trackersLogger;
+ private final TrackersRepository trackersRepository;
+ private final WhitelistRepository whitelistRepository;
+
+ private int eBrowserAppUid = -1;
+
+ private final String TAG = DNSBlockerRunnable.class.getName();
+ private static final String SOCKET_NAME = "foundation.e.advancedprivacy";
+
+
+ public DNSBlockerRunnable(Context ct, TrackersLogger trackersLogger, TrackersRepository trackersRepository, WhitelistRepository whitelistRepository) {
+ this.trackersLogger = trackersLogger;
+ this.trackersRepository = trackersRepository;
+ this.whitelistRepository = whitelistRepository;
+ initEBrowserDoTFix(ct);
+ }
+
+ public synchronized void stop() {
+ stopped = true;
+ closeSocket();
+ }
+
+ private void closeSocket() {
+ // Known bug and workaround that LocalServerSocket::close is not working well
+ // https://issuetracker.google.com/issues/36945762
+ if (resolverReceiver != null) {
+ try {
+ Os.shutdown(resolverReceiver.getFileDescriptor(), OsConstants.SHUT_RDWR);
+ resolverReceiver.close();
+ resolverReceiver = null;
+ } catch (ErrnoException e) {
+ if (e.errno != OsConstants.EBADF) {
+ Log.w(TAG, "Socket already closed");
+ } else {
+ Log.e(TAG, "Exception: cannot close DNS port on stop" + SOCKET_NAME + "!", e);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception: cannot close DNS port on stop" + SOCKET_NAME + "!", e);
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ resolverReceiver = new LocalServerSocket(SOCKET_NAME);
+ } catch (IOException eio) {
+ Log.e(TAG, "Exception:Cannot open DNS port " + SOCKET_NAME + "!", eio);
+ return;
+ }
+ Log.d(TAG, "DNSFilterProxy running on port " + SOCKET_NAME + "!");
+
+ while (!stopped) {
+ try {
+ LocalSocket socket = resolverReceiver.accept();
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ String line = reader.readLine();
+ String[] params = line.split(",");
+ OutputStream output = socket.getOutputStream();
+ PrintWriter writer = new PrintWriter(output, true);
+
+ String domainName = params[0];
+ int appUid = Integer.parseInt(params[1]);
+ boolean isBlocked = false;
+
+ if (isEBrowserDoTBlockFix(appUid, domainName)) {
+ isBlocked = true;
+ } else if (trackersRepository.isTracker(domainName)) {
+ String trackerId = trackersRepository.getTrackerId(domainName);
+
+ if (shouldBlock(appUid, trackerId)) {
+ writer.println("block");
+ isBlocked = true;
+ }
+ trackersLogger.logAccess(trackerId, appUid, isBlocked);
+ }
+
+ if (!isBlocked) {
+ writer.println("pass");
+ }
+ socket.close();
+ // Printing bufferedreader data
+ } catch (IOException e) {
+ Log.w(TAG, "Exception while listening DNS resolver", e);
+ }
+ }
+ }
+
+ private void initEBrowserDoTFix(Context context) {
+ try {
+ eBrowserAppUid = context.getPackageManager().getApplicationInfo("foundation.e.browser", 0).uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.i(TAG, "no E Browser package found.");
+ }
+ }
+
+ private static final String E_BROWSER_DOT_SERVER = "chrome.cloudflare-dns.com";
+ private boolean isEBrowserDoTBlockFix(int appUid, String hostname) {
+ return appUid == eBrowserAppUid && E_BROWSER_DOT_SERVER.equals(hostname);
+ }
+
+ private boolean shouldBlock(int appUid, String trackerId) {
+ return whitelistRepository.isBlockingEnabled() &&
+ !whitelistRepository.isAppWhiteListed(appUid) &&
+ !whitelistRepository.isTrackerWhiteListedForApp(trackerId, appUid);
+ }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.java
new file mode 100644
index 0000000000000000000000000000000000000000..6250621a110e861b809191c5d902a1395a2053e5
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.java
@@ -0,0 +1,86 @@
+/*
+ Copyright (C) 2021 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+
+package foundation.e.privacymodules.trackers;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import foundation.e.privacymodules.trackers.data.TrackersRepository;
+import foundation.e.privacymodules.trackers.data.WhitelistRepository;
+
+public class DNSBlockerService extends Service {
+ private static final String TAG = "DNSBlockerService";
+ private static DNSBlockerRunnable sDNSBlocker;
+ private TrackersLogger trackersLogger;
+
+ public static final String ACTION_START = "foundation.e.privacymodules.trackers.intent.action.START";
+
+ public static final String EXTRA_ENABLE_NOTIFICATION = "foundation.e.privacymodules.trackers.intent.extra.ENABLED_NOTIFICATION";
+
+ public DNSBlockerService() {
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // TODO: Return the communication channel to the service.
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent != null && intent.getBooleanExtra(EXTRA_ENABLE_NOTIFICATION, true)) {
+ ForegroundStarter.startForeground(this);
+ }
+
+ if (intent != null && ACTION_START.equals(intent.getAction())) {
+ stop();
+ start();
+ }
+
+ return START_STICKY;
+ }
+
+ private void start() {
+ try {
+ trackersLogger = new TrackersLogger(this);
+ sDNSBlocker = new DNSBlockerRunnable(this, trackersLogger,
+ TrackersRepository.getInstance(), WhitelistRepository.getInstance(this));
+
+ new Thread(sDNSBlocker).start();
+ } catch(Exception e) {
+ Log.e(TAG, "Error while starting DNSBlocker service", e);
+ stop();
+ }
+ }
+
+ private void stop() {
+ if (sDNSBlocker != null) {
+ sDNSBlocker.stop();
+ }
+ sDNSBlocker = null;
+ if (trackersLogger != null) {
+ trackersLogger.stop();
+ }
+ trackersLogger = null;
+ }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.java
new file mode 100644
index 0000000000000000000000000000000000000000..1563163563a6ff6622ca080ea9bb5c0ce52619dd
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.java
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2021 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+package foundation.e.privacymodules.trackers;
+
+import static android.content.Context.NOTIFICATION_SERVICE;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.os.Build;
+
+
+public class ForegroundStarter {
+ private static final String NOTIFICATION_CHANNEL_ID = "blocker_service";
+ public static void startForeground(Service service){
+ NotificationManager mNotificationManager = (NotificationManager) service.getSystemService(NOTIFICATION_SERVICE);
+ if (Build.VERSION.SDK_INT >= 26) {
+ mNotificationManager.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW));
+ Notification notification = new Notification.Builder(service, NOTIFICATION_CHANNEL_ID)
+ .setContentTitle("Trackers filter").build();
+ service.startForeground(1337, notification);
+ }
+ }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.java
new file mode 100644
index 0000000000000000000000000000000000000000..37102530d282fc7ea2a26636e2f380d079f726b2
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.java
@@ -0,0 +1,83 @@
+/*
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+package foundation.e.privacymodules.trackers;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+import foundation.e.privacymodules.trackers.data.StatsRepository;
+
+
+public class TrackersLogger {
+ private static final String TAG = "TrackerModule";
+ private StatsRepository statsRepository;
+
+ private LinkedBlockingQueue queue;
+ private boolean stopped = false;
+
+
+ public TrackersLogger(Context context) {
+ statsRepository = StatsRepository.getInstance(context);
+ queue = new LinkedBlockingQueue();
+ startWriteLogLoop();
+ }
+
+ public void stop() {
+ stopped = true;
+ }
+
+ public void logAccess(String trackerId, int appId, boolean wasBlocked) {
+ queue.offer(new DetectedTracker(trackerId, appId, wasBlocked));
+ }
+
+ private void startWriteLogLoop() {
+ Runnable writeLogRunner = new Runnable() {
+ @Override
+ public void run() {
+ while(!stopped) {
+ try {
+ logAccess(queue.take());
+ } catch (InterruptedException e) {
+ Log.e(TAG, "writeLogLoop detectedTrackersQueue.take() interrupted: ", e);
+ }
+ }
+ }
+ };
+ new Thread(writeLogRunner).start();
+ }
+
+
+ public void logAccess(DetectedTracker detectedTracker) {
+ statsRepository.logAccess(detectedTracker.trackerId, detectedTracker.appUid, detectedTracker.wasBlocked);
+ }
+
+ private class DetectedTracker {
+ String trackerId;
+ int appUid;
+ boolean wasBlocked;
+
+ public DetectedTracker(String trackerId, int appUid, boolean wasBlocked) {
+ this.trackerId = trackerId;
+ this.appUid = appUid;
+ this.wasBlocked = wasBlocked;
+ }
+ }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..ea62766bd733ee6a4851f76ec37f3161090138ff
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.java
@@ -0,0 +1,125 @@
+/*
+ Copyright (C) 2021 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+
+package foundation.e.privacymodules.trackers.api;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import foundation.e.privacymodules.permissions.data.ApplicationDescription;
+import foundation.e.privacymodules.trackers.IBlockTrackersPrivacyModule;
+import foundation.e.privacymodules.trackers.Tracker;
+import foundation.e.privacymodules.trackers.data.TrackersRepository;
+import foundation.e.privacymodules.trackers.data.WhitelistRepository;
+
+public class BlockTrackersPrivacyModule implements IBlockTrackersPrivacyModule {
+
+ private final Context mContext;
+ private List mListeners = new ArrayList<>();
+ private static BlockTrackersPrivacyModule sBlockTrackersPrivacyModule;
+
+ private TrackersRepository trackersRepository;
+ private WhitelistRepository whitelistRepository;
+
+ public BlockTrackersPrivacyModule(Context context) {
+ mContext = context;
+ trackersRepository = TrackersRepository.getInstance();
+ whitelistRepository = WhitelistRepository.getInstance(mContext);
+ }
+
+ public static BlockTrackersPrivacyModule getInstance(Context ct){
+ if(sBlockTrackersPrivacyModule == null){
+ sBlockTrackersPrivacyModule = new BlockTrackersPrivacyModule(ct);
+ }
+ return sBlockTrackersPrivacyModule;
+ }
+
+ @Override
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void clearListeners() {
+ mListeners.clear();
+ }
+
+ @Override
+ public void disableBlocking() {
+ whitelistRepository.setBlockingEnabled(false);
+ for(Listener listener:mListeners){
+ listener.onBlockingToggle(false);
+ }
+ }
+
+ @Override
+ public void enableBlocking() {
+ whitelistRepository.setBlockingEnabled(true);
+ for(Listener listener:mListeners){
+ listener.onBlockingToggle(true);
+ }
+ }
+
+ @Override
+ public List getWhiteList(int appUid) {
+ List trackers = new ArrayList();
+ for (String trackerId: whitelistRepository.getWhiteListForApp(appUid)) {
+ trackers.add(trackersRepository.getTracker(trackerId));
+ }
+ return trackers;
+ }
+
+ @Override
+ public List getWhiteListedApp() {
+ return whitelistRepository.getWhiteListedApp();
+ }
+
+ @Override
+ public boolean isBlockingEnabled() {
+ return whitelistRepository.isBlockingEnabled();
+ }
+
+ @Override
+ public boolean isWhiteListEmpty() {
+ return whitelistRepository.areWhiteListEmpty();
+ }
+
+ @Override
+ public boolean isWhitelisted(int appUid) {
+ return whitelistRepository.isAppWhiteListed(appUid);
+ }
+
+ @Override
+ public void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void setWhiteListed(Tracker tracker, int appUid, boolean isWhiteListed) {
+ whitelistRepository.setWhiteListed(tracker, appUid, isWhiteListed);
+ }
+
+ @Override
+ public void setWhiteListed(int appUid, boolean isWhiteListed) {
+ whitelistRepository.setWhiteListed(appUid, isWhiteListed);
+ }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..38b2c8fdb0d781a2abae0cd85200a9565e640843
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.java
@@ -0,0 +1,150 @@
+/*
+ Copyright (C) 2021 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+
+package foundation.e.privacymodules.trackers.api;
+
+import android.content.Context;
+import android.content.Intent;
+
+
+import org.jetbrains.annotations.NotNull;
+
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import foundation.e.privacymodules.trackers.DNSBlockerService;
+import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule;
+import foundation.e.privacymodules.trackers.Tracker;
+import foundation.e.privacymodules.trackers.data.StatsRepository;
+import foundation.e.privacymodules.trackers.data.TrackersRepository;
+import kotlin.Pair;
+
+public class TrackTrackersPrivacyModule implements ITrackTrackersPrivacyModule {
+
+ private static TrackTrackersPrivacyModule sTrackTrackersPrivacyModule;
+ private final Context mContext;
+ private StatsRepository statsRepository;
+ private List mListeners = new ArrayList();
+
+ public TrackTrackersPrivacyModule(Context context) {
+ mContext = context;
+ statsRepository = StatsRepository.getInstance(context);
+ statsRepository.setNewDataCallback((newData) -> {
+ mListeners.forEach((listener) -> { listener.onNewData(); });
+ });
+ }
+
+ public static TrackTrackersPrivacyModule getInstance(Context context){
+ if(sTrackTrackersPrivacyModule == null){
+ sTrackTrackersPrivacyModule = new TrackTrackersPrivacyModule(context);
+ }
+ return sTrackTrackersPrivacyModule;
+ }
+
+ public void start(List trackers, boolean enableNotification) {
+ TrackersRepository.getInstance().setTrackersList(trackers);
+
+ Intent intent = new Intent(mContext, DNSBlockerService.class);
+ intent.setAction(DNSBlockerService.ACTION_START);
+ intent.putExtra(DNSBlockerService.EXTRA_ENABLE_NOTIFICATION, enableNotification);
+ mContext.startService(intent);
+ }
+
+ @NotNull
+ @Override
+ public List> getPastDayTrackersCalls() {
+ return statsRepository.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS);
+ }
+
+ @Override
+ public List> getPastMonthTrackersCalls() {
+ return statsRepository.getTrackersCallsOnPeriod(30, ChronoUnit.DAYS);
+ }
+
+ @Override
+ public List> getPastYearTrackersCalls() {
+ return statsRepository.getTrackersCallsOnPeriod(12, ChronoUnit.MONTHS);
+ }
+
+ @Override
+ public int getTrackersCount() {
+ return statsRepository.getContactedTrackersCount();
+ }
+
+ @Override
+ public Map getTrackersCountByApp() {
+ return statsRepository.getContactedTrackersCountByApp();
+ }
+
+ @Override
+ public List getTrackersForApp(int i) {
+ return statsRepository.getAllTrackersOfApp(i);
+ }
+
+
+ @Override
+ public int getPastDayTrackersCount() {
+ return statsRepository.getActiveTrackersByPeriod(24, ChronoUnit.HOURS);
+ }
+
+ @Override
+ public int getPastMonthTrackersCount() {
+ return statsRepository.getActiveTrackersByPeriod(30, ChronoUnit.DAYS);
+ }
+
+ @Override
+ public int getPastYearTrackersCount() {
+ return statsRepository.getActiveTrackersByPeriod(12, ChronoUnit.MONTHS);
+ }
+
+ @Override
+ public int getPastDayMostLeakedApp() {
+ return statsRepository.getMostLeakedApp(24, ChronoUnit.HOURS);
+ }
+
+ @NotNull
+ @Override
+ public Map> getPastDayTrackersCallsByApps() {
+ return statsRepository.getCallsByApps(24, ChronoUnit.HOURS);
+ }
+
+ @NotNull
+ @Override
+ public Pair getPastDayTrackersCallsForApp(int appUid) {
+ return statsRepository.getCalls(appUid, 24, ChronoUnit.HOURS);
+ }
+
+ @Override
+ public void addListener(ITrackTrackersPrivacyModule.Listener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(ITrackTrackersPrivacyModule.Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void clearListeners() {
+ mListeners.clear();
+ }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.java
new file mode 100644
index 0000000000000000000000000000000000000000..06501149889b809042770a6a9b96d93a54476910
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.java
@@ -0,0 +1,507 @@
+/*
+ Copyright (C) 2021 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+
+package foundation.e.privacymodules.trackers.data;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import foundation.e.privacymodules.trackers.Tracker;
+import kotlin.Pair;
+
+public class StatsDatabase extends SQLiteOpenHelper {
+ public static final int DATABASE_VERSION = 1;
+ public static final String DATABASE_NAME = "TrackerFilterStats.db";
+ private final Object lock = new Object();
+ private TrackersRepository trackersRepository;
+
+ public StatsDatabase(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ trackersRepository = TrackersRepository.getInstance();
+ }
+
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(SQL_CREATE_TABLE);
+ }
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ onCreate(db);
+ }
+
+
+ public static class AppTrackerEntry implements BaseColumns {
+ public static final String TABLE_NAME = "tracker_filter_stats";
+ public static final String COLUMN_NAME_TIMESTAMP = "timestamp";
+ public static final String COLUMN_NAME_TRACKER = "tracker";
+ public static final String COLUMN_NAME_APP_UID = "app_uid";
+ public static final String COLUMN_NAME_NUMBER_CONTACTED = "sum_contacted";
+ public static final String COLUMN_NAME_NUMBER_BLOCKED = "sum_blocked";
+
+ }
+
+ String[] projection = {
+ AppTrackerEntry.COLUMN_NAME_TIMESTAMP,
+ AppTrackerEntry.COLUMN_NAME_APP_UID,
+ AppTrackerEntry.COLUMN_NAME_TRACKER,
+ AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED,
+ AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED
+ };
+
+ private static final String SQL_CREATE_TABLE =
+ "CREATE TABLE " + AppTrackerEntry.TABLE_NAME + " (" +
+ AppTrackerEntry._ID + " INTEGER PRIMARY KEY," +
+ AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " INTEGER,"+
+ AppTrackerEntry.COLUMN_NAME_APP_UID + " INTEGER," +
+ AppTrackerEntry.COLUMN_NAME_TRACKER + " TEXT," +
+ AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED + " INTEGER," +
+ AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED + " INTEGER)";
+
+ private static final String PROJECTION_NAME_PERIOD = "period";
+ private static final String PROJECTION_NAME_CONTACTED_SUM = "contactedsum";
+ private static final String PROJECTION_NAME_BLOCKED_SUM = "blockedsum";
+ private static final String PROJECTION_NAME_LEAKED_SUM = "leakedsum";
+ private static final String PROJECTION_NAME_TRACKERS_COUNT = "trackerscount";
+
+ private HashMap> getCallsByPeriod(
+ int periodsCount,
+ TemporalUnit periodUnit,
+ String sqlitePeriodFormat
+ ) {
+ synchronized (lock) {
+ long minTimestamp = getPeriodStartTs(periodsCount, periodUnit);
+
+ SQLiteDatabase db = getReadableDatabase();
+
+ String selection = AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " >= ?";
+ String[] selectionArg = new String[]{"" + minTimestamp};
+ String projection =
+ AppTrackerEntry.COLUMN_NAME_TIMESTAMP + ", " +
+ "STRFTIME('" + sqlitePeriodFormat + "', DATETIME(" + AppTrackerEntry.COLUMN_NAME_TIMESTAMP + ", 'unixepoch', 'localtime')) " + PROJECTION_NAME_PERIOD + "," +
+ "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED + ") " + PROJECTION_NAME_CONTACTED_SUM + "," +
+ "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED + ") " + PROJECTION_NAME_BLOCKED_SUM;
+ Cursor cursor = db.rawQuery("SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME
+ + " WHERE " + selection +
+ " GROUP BY " + PROJECTION_NAME_PERIOD +
+ " ORDER BY " + AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " DESC" +
+ " LIMIT " + periodsCount, selectionArg);
+
+ HashMap> callsByPeriod = new HashMap<>();
+ while (cursor.moveToNext()) {
+ int contacted = cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_CONTACTED_SUM));
+ int blocked = cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_BLOCKED_SUM));
+
+ callsByPeriod.put(
+ cursor.getString(cursor.getColumnIndex(PROJECTION_NAME_PERIOD)),
+ new Pair(blocked, contacted - blocked)
+ );
+ }
+
+ cursor.close();
+ db.close();
+
+ return callsByPeriod;
+ }
+ }
+
+ private List> callsByPeriodToPeriodsList(
+ Map> callsByPeriod,
+ int periodsCount,
+ TemporalUnit periodUnit,
+ String javaPeriodFormat
+ ) {
+ ZonedDateTime currentDate = ZonedDateTime.now().minus(periodsCount, periodUnit);
+ DateTimeFormatter formater = DateTimeFormatter.ofPattern(javaPeriodFormat);
+
+ List> calls = new ArrayList(periodsCount);
+ for (int i = 0; i < periodsCount; i++) {
+ currentDate = currentDate.plus(1, periodUnit);
+
+ String currentPeriod = formater.format(currentDate);
+ calls.add(callsByPeriod.getOrDefault(currentPeriod, new Pair(0, 0)));
+ }
+ return calls;
+ }
+
+ public List> getTrackersCallsOnPeriod(int periodsCount, TemporalUnit periodUnit) {
+ String sqlitePeriodFormat = "%Y%m";
+ String javaPeriodFormat = "yyyyMM";
+
+ if (periodUnit == ChronoUnit.MONTHS) {
+ sqlitePeriodFormat = "%Y%m";
+ javaPeriodFormat = "yyyyMM";
+ } else if (periodUnit == ChronoUnit.DAYS) {
+ sqlitePeriodFormat = "%Y%m%d";
+ javaPeriodFormat = "yyyyMMdd";
+ } else if (periodUnit == ChronoUnit.HOURS) {
+ sqlitePeriodFormat = "%Y%m%d%H";
+ javaPeriodFormat = "yyyyMMddHH";
+ }
+
+ Map> callsByPeriod = getCallsByPeriod(periodsCount, periodUnit, sqlitePeriodFormat);
+ return callsByPeriodToPeriodsList(callsByPeriod, periodsCount, periodUnit, javaPeriodFormat);
+ }
+
+
+
+ public int getActiveTrackersByPeriod(int periodsCount, TemporalUnit periodUnit) {
+ synchronized (lock) {
+ long minTimestamp = getPeriodStartTs(periodsCount, periodUnit);
+
+
+ SQLiteDatabase db = getWritableDatabase();
+
+ String selection = AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " >= ? AND " +
+ AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED + " > " + AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED;
+ String[] selectionArg = new String[]{"" + minTimestamp};
+ String projection =
+ "COUNT(DISTINCT " + AppTrackerEntry.COLUMN_NAME_TRACKER + ") " + PROJECTION_NAME_TRACKERS_COUNT;
+ Cursor cursor = db.rawQuery("SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME
+ + " WHERE " + selection, selectionArg);
+
+ int count = 0;
+
+ if (cursor.moveToNext()) {
+ count = cursor.getInt(0);
+ }
+
+ cursor.close();
+ db.close();
+
+ return count;
+ }
+
+ }
+
+ public int getContactedTrackersCount() {
+ synchronized (lock) {
+ SQLiteDatabase db = getReadableDatabase();
+ String projection =
+ "COUNT(DISTINCT " + AppTrackerEntry.COLUMN_NAME_TRACKER + ") " + PROJECTION_NAME_TRACKERS_COUNT;
+
+ Cursor cursor = db.rawQuery(
+ "SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME,
+ new String[]{});
+
+ int count = 0;
+
+ if (cursor.moveToNext()) {
+ count = cursor.getInt(0);
+ }
+
+ cursor.close();
+ db.close();
+
+ return count;
+ }
+ }
+
+
+ public Map getContactedTrackersCountByApp() {
+ synchronized (lock) {
+ SQLiteDatabase db = getReadableDatabase();
+ String projection =
+ AppTrackerEntry.COLUMN_NAME_APP_UID + ", " +
+ "COUNT(DISTINCT " + AppTrackerEntry.COLUMN_NAME_TRACKER + ") " + PROJECTION_NAME_TRACKERS_COUNT;
+
+ Cursor cursor = db.rawQuery(
+ "SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME +
+ " GROUP BY " + AppTrackerEntry.COLUMN_NAME_APP_UID,
+ new String[]{});
+
+ HashMap countByApp = new HashMap();
+
+ while (cursor.moveToNext()) {
+ countByApp.put(
+ cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_APP_UID)),
+ cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_TRACKERS_COUNT))
+ );
+ }
+
+ cursor.close();
+ db.close();
+
+ return countByApp;
+ }
+ }
+
+ public Map> getCallsByApps(int periodCount, TemporalUnit periodUnit) {
+ synchronized (lock) {
+ long minTimestamp = getPeriodStartTs(periodCount, periodUnit);
+
+ SQLiteDatabase db = getReadableDatabase();
+
+ String selection = AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " >= ?";
+ String[] selectionArg = new String[]{"" + minTimestamp};
+ String projection =
+ AppTrackerEntry.COLUMN_NAME_APP_UID + ", " +
+ "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED + ") " + PROJECTION_NAME_CONTACTED_SUM + "," +
+ "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED + ") " + PROJECTION_NAME_BLOCKED_SUM;
+
+ Cursor cursor = db.rawQuery(
+ "SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME +
+ " WHERE " + selection +
+ " GROUP BY " + AppTrackerEntry.COLUMN_NAME_APP_UID,
+ selectionArg);
+
+
+ HashMap> callsByApp = new HashMap<>();
+
+ while (cursor.moveToNext()) {
+ int contacted = cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_CONTACTED_SUM));
+ int blocked = cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_BLOCKED_SUM));
+
+ callsByApp.put(
+ cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_APP_UID)),
+ new Pair(blocked, contacted - blocked)
+ );
+ }
+
+ cursor.close();
+ db.close();
+
+ return callsByApp;
+ }
+ }
+
+ public Pair getCalls(int appUid, int periodCount, TemporalUnit periodUnit) {
+ synchronized (lock) {
+ long minTimestamp = getPeriodStartTs(periodCount, periodUnit);
+
+ SQLiteDatabase db = getReadableDatabase();
+
+ String selection =
+ AppTrackerEntry.COLUMN_NAME_APP_UID + " = ? AND " +
+ AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " >= ?";
+ String[] selectionArg = new String[]{ "" + appUid, "" + minTimestamp };
+ String projection =
+ "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED + ") " + PROJECTION_NAME_CONTACTED_SUM + "," +
+ "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED + ") " + PROJECTION_NAME_BLOCKED_SUM;
+
+ Cursor cursor = db.rawQuery(
+ "SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME +
+ " WHERE " + selection,
+ selectionArg);
+
+ HashMap> callsByApp = new HashMap<>();
+
+ Pair calls = new Pair(0, 0);
+
+ if (cursor.moveToNext()) {
+ int contacted = cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_CONTACTED_SUM));
+ int blocked = cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_BLOCKED_SUM));
+
+ calls = new Pair(blocked, contacted - blocked);
+ }
+
+ cursor.close();
+ db.close();
+
+ return calls;
+ }
+ }
+
+ public int getMostLeakedApp(int periodCount, TemporalUnit periodUnit) {
+ synchronized (lock) {
+ long minTimestamp = getPeriodStartTs(periodCount, periodUnit);
+
+ SQLiteDatabase db = getReadableDatabase();
+
+ String selection = AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " >= ?";
+ String[] selectionArg = new String[]{"" + minTimestamp};
+ String projection =
+ AppTrackerEntry.COLUMN_NAME_APP_UID + ", " +
+ "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED +
+ " - " + AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED +
+ ") " + PROJECTION_NAME_LEAKED_SUM;
+
+ Cursor cursor = db.rawQuery(
+ "SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME +
+ " WHERE " + selection +
+ " GROUP BY " + AppTrackerEntry.COLUMN_NAME_APP_UID +
+ " ORDER BY " + PROJECTION_NAME_LEAKED_SUM + " DESC LIMIT 1",
+ selectionArg);
+
+
+ int appUid = 0;
+ if (cursor.moveToNext()) {
+ appUid = cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_APP_UID));
+ }
+
+ cursor.close();
+ db.close();
+
+ return appUid;
+ }
+ }
+
+ private long getCurrentHourTs() {
+ long hourInMs = TimeUnit.HOURS.toMillis(1L);
+ long hourInS = TimeUnit.HOURS.toSeconds(1L);
+ return (System.currentTimeMillis() / hourInMs) * hourInS;
+ }
+
+ private long getPeriodStartTs(
+ int periodsCount,
+ TemporalUnit periodUnit
+ ) {
+
+ ZonedDateTime start = ZonedDateTime.now()
+ .minus(periodsCount, periodUnit)
+ .plus(1, periodUnit);
+
+ TemporalUnit truncatePeriodUnit = periodUnit;
+ if (periodUnit == ChronoUnit.MONTHS) {
+ start = start.withDayOfMonth(1);
+ truncatePeriodUnit = ChronoUnit.DAYS;
+ }
+
+ return start.truncatedTo(truncatePeriodUnit).toEpochSecond();
+ }
+
+ public void logAccess(String trackerId, int appUid, boolean blocked){
+ synchronized (lock) {
+ long currentHour = getCurrentHourTs();
+ SQLiteDatabase db = getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(AppTrackerEntry.COLUMN_NAME_APP_UID, appUid);
+ values.put(AppTrackerEntry.COLUMN_NAME_TRACKER, trackerId);
+ values.put(AppTrackerEntry.COLUMN_NAME_TIMESTAMP, currentHour);
+
+ /*String query = "UPDATE product SET "+AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED+" = "+AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED+" + 1 ";
+ if(blocked)
+ query+=AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED+" = "+AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED+" + 1 ";
+*/
+ String selection =
+ AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " = ? AND " +
+ AppTrackerEntry.COLUMN_NAME_APP_UID + " = ? AND " +
+ AppTrackerEntry.COLUMN_NAME_TRACKER + " = ? ";
+
+ String[] selectionArg = new String[]{"" + currentHour, "" + appUid, trackerId};
+
+ Cursor cursor = db.query(
+ AppTrackerEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArg,
+ null,
+ null,
+ null
+ );
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ StatEntry entry = cursorToEntry(cursor);
+ if (blocked)
+ values.put(AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED, entry.sum_blocked + 1);
+ else
+ values.put(AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED, entry.sum_blocked);
+ values.put(AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED, entry.sum_contacted + 1);
+ db.update(AppTrackerEntry.TABLE_NAME, values, selection, selectionArg);
+
+ // db.execSQL(query, new String[]{""+hour, ""+day, ""+month, ""+year, ""+appUid, ""+trackerId});
+ } else {
+
+ if (blocked)
+ values.put(AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED, 1);
+ else
+ values.put(AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED, 0);
+ values.put(AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED, 1);
+
+
+ long newRowId = db.insert(AppTrackerEntry.TABLE_NAME, null, values);
+ }
+
+ cursor.close();
+ db.close();
+ }
+ }
+
+
+ private StatEntry cursorToEntry(Cursor cursor){
+ StatEntry entry = new StatEntry();
+ entry.timestamp = cursor.getLong(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_TIMESTAMP));
+ entry.app_uid = cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_APP_UID));
+ entry.sum_blocked = cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED));
+ entry.sum_contacted = cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED));
+ entry.tracker = cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_TRACKER));
+ return entry;
+ }
+
+ public List getAllTrackersOfApp(int appUid){
+ synchronized (lock) {
+ String[] columns = { AppTrackerEntry.COLUMN_NAME_TRACKER, AppTrackerEntry.COLUMN_NAME_APP_UID };
+ String selection = null;
+ String[] selectionArg = null;
+ if (appUid >= 0) {
+ selection = AppTrackerEntry.COLUMN_NAME_APP_UID + " = ?";
+ selectionArg = new String[]{"" + appUid};
+ }
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor cursor = db.query(
+ true,
+ AppTrackerEntry.TABLE_NAME,
+ columns,
+ selection,
+ selectionArg,
+ null,
+ null,
+ null,
+ null
+ );
+ List trackers = new ArrayList<>();
+
+ while (cursor.moveToNext()) {
+ String trackerId = cursor.getString(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_TRACKER));
+ Tracker tracker = trackersRepository.getTracker(trackerId);
+
+ if (tracker != null) {
+ trackers.add(tracker);
+ }
+ }
+ cursor.close();
+ db.close();
+ return trackers;
+ }
+ }
+
+ public List getAllTrackers(){
+ return getAllTrackersOfApp(-1);
+ }
+
+ public static class StatEntry {
+ int app_uid;
+ int sum_contacted;
+ int sum_blocked;
+ long timestamp;
+ int tracker;
+ }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..bfe688f16b63d5611960aa4cdc73f7a4eec65a7f
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.java
@@ -0,0 +1,95 @@
+/*
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+package foundation.e.privacymodules.trackers.data;
+
+import android.content.Context;
+
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import foundation.e.privacymodules.trackers.Tracker;
+import kotlin.Pair;
+
+public class StatsRepository {
+ private static StatsRepository instance;
+
+ private StatsDatabase database;
+
+ private Consumer newDataCallback = null;
+
+ private StatsRepository(Context context) {
+ database = new StatsDatabase(context);
+ }
+
+ public static StatsRepository getInstance(Context context) {
+ if (instance == null) {
+ instance = new StatsRepository(context);
+ }
+ return instance;
+ }
+
+ public void setNewDataCallback(Consumer callback) {
+ newDataCallback = callback;
+ }
+
+ public void logAccess(String trackerId, int appUid, boolean blocked) {
+ database.logAccess(trackerId, appUid, blocked);
+ if (newDataCallback != null) {
+ newDataCallback.accept(true);
+ }
+ }
+
+ public List> getTrackersCallsOnPeriod(int periodsCount, TemporalUnit periodUnit) {
+ return database.getTrackersCallsOnPeriod(periodsCount, periodUnit);
+ }
+
+ public int getActiveTrackersByPeriod(int periodsCount, TemporalUnit periodUnit) {
+ return database.getActiveTrackersByPeriod(periodsCount, periodUnit);
+ }
+
+ public Map getContactedTrackersCountByApp() {
+ return database.getContactedTrackersCountByApp();
+ }
+
+ public int getContactedTrackersCount() {
+ return database.getContactedTrackersCount();
+ }
+
+ public List getAllTrackersOfApp(int app_uid) {
+ return database.getAllTrackersOfApp(app_uid);
+ }
+
+ public Map> getCallsByApps(int periodCount, TemporalUnit periodUnit) {
+ return database.getCallsByApps(periodCount, periodUnit);
+ }
+
+ public Pair getCalls(int appUid, int periodCount, TemporalUnit periodUnit) {
+ return database.getCalls(appUid, periodCount, periodUnit);
+ }
+
+
+ public int getMostLeakedApp(int periodCount, TemporalUnit periodUnit) {
+ return database.getMostLeakedApp(periodCount, periodUnit);
+ }
+}
\ No newline at end of file
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..5c77c7a3dfce2da79967fdaa532dc3b8e1ff793e
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.java
@@ -0,0 +1,71 @@
+/*
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+package foundation.e.privacymodules.trackers.data;
+
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import foundation.e.privacymodules.trackers.Tracker;
+
+public class TrackersRepository {
+ private static TrackersRepository instance;
+
+ private TrackersRepository() { }
+
+ public static TrackersRepository getInstance() {
+ if (instance == null) {
+ instance = new TrackersRepository();
+ }
+ return instance;
+ }
+
+ private Map trackersById = new HashMap();
+ private Map hostnameToId = new HashMap();
+
+ public void setTrackersList(List list) {
+ Map trackersById = new HashMap();
+ Map hostnameToId = new HashMap();
+
+ for (Tracker tracker: list) {
+ trackersById.put(tracker.getId(), tracker);
+
+ for (String hostname: tracker.getHostnames()) {
+ hostnameToId.put(hostname, tracker.getId());
+ }
+ }
+
+ this.trackersById = trackersById;
+ this.hostnameToId = hostnameToId;
+ }
+
+ public boolean isTracker(String hostname) {
+ return hostnameToId.containsKey(hostname);
+ }
+
+ public String getTrackerId(String hostname) {
+ return hostnameToId.get(hostname);
+ }
+
+ public Tracker getTracker(String id) {
+ return trackersById.get(id);
+ }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..9bfca7f45334e0e770ac0566d9f68210092a7308
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.java
@@ -0,0 +1,153 @@
+/*
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+package foundation.e.privacymodules.trackers.data;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import foundation.e.privacymodules.trackers.Tracker;
+
+public class WhitelistRepository {
+ private static final String SHARED_PREFS_FILE = "trackers_whitelist.prefs";
+ private static final String KEY_BLOKING_ENABLED = "blocking_enabled";
+ private static final String KEY_APPS_WHITELIST = "apps_whitelist";
+ private static final String KEY_APP_TRACKERS_WHITELIST_PREFIX = "app_trackers_whitelist_";
+ private static WhitelistRepository instance;
+
+ private boolean isBlockingEnabled = false;
+ private Set appsWhitelist;
+ private Map> trackersWhitelistByApp = new HashMap();
+
+ private SharedPreferences prefs;
+ private WhitelistRepository(Context context) {
+ prefs = context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ reloadCache();
+ }
+
+ public static WhitelistRepository getInstance(Context context) {
+ if (instance == null) {
+ instance = new WhitelistRepository(context);
+ }
+ return instance;
+ }
+
+ private void reloadCache() {
+ isBlockingEnabled = prefs.getBoolean(KEY_BLOKING_ENABLED, false);
+ reloadAppsWhiteList();
+ reloadAllAppTrackersWhiteList();
+ }
+
+ private void reloadAppsWhiteList() {
+ HashSet appWhiteList = new HashSet();
+ for (String appUid: prefs.getStringSet(KEY_APPS_WHITELIST, new HashSet())) {
+ try {
+ appWhiteList.add(Integer.parseInt(appUid));
+ } catch (Exception e) { }
+ }
+ this.appsWhitelist = appWhiteList;
+ }
+
+ private void reloadAppTrackersWhiteList(int appUid) {
+ String key = buildAppTrackersKey(appUid);
+ trackersWhitelistByApp.put(appUid, prefs.getStringSet(key, new HashSet()));
+ }
+
+ private void reloadAllAppTrackersWhiteList() {
+ trackersWhitelistByApp.clear();
+ for (String key: prefs.getAll().keySet()) {
+ if (key.startsWith(KEY_APP_TRACKERS_WHITELIST_PREFIX)) {
+ int appUid = Integer.parseInt(key.substring(KEY_APP_TRACKERS_WHITELIST_PREFIX.length()));
+ reloadAppTrackersWhiteList(appUid);
+ }
+ }
+ }
+
+ public boolean isBlockingEnabled() { return isBlockingEnabled; }
+
+ public void setBlockingEnabled(boolean enabled) {
+ prefs.edit().putBoolean(KEY_BLOKING_ENABLED, enabled).apply();
+ isBlockingEnabled = enabled;
+ }
+
+ public void setWhiteListed(int appUid, boolean isWhiteListed) {
+ Set current = new HashSet(prefs.getStringSet(KEY_APPS_WHITELIST, new HashSet()));
+ if (isWhiteListed) {
+ current.add("" + appUid);
+ } else {
+ current.remove("" + appUid);
+ }
+
+ prefs.edit().putStringSet(KEY_APPS_WHITELIST, current).commit();
+ reloadAppsWhiteList();
+ }
+
+ private String buildAppTrackersKey(int appUid) {
+ return KEY_APP_TRACKERS_WHITELIST_PREFIX + appUid;
+ }
+
+ public void setWhiteListed(Tracker tracker, int appUid, boolean isWhiteListed) {
+ Set trackers;
+ if (trackersWhitelistByApp.containsKey(appUid)) {
+ trackers = trackersWhitelistByApp.get(appUid);
+ } else {
+ trackers = new HashSet();
+ trackersWhitelistByApp.put(appUid, trackers);
+ }
+ if (isWhiteListed) {
+ trackers.add(tracker.getId());
+ } else {
+ trackers.remove(tracker.getId());
+ }
+
+ prefs.edit().putStringSet(buildAppTrackersKey(appUid), trackers).commit();
+ }
+
+ public boolean isAppWhiteListed(int appUid) {
+ return appsWhitelist.contains(appUid);
+ }
+
+ public boolean isTrackerWhiteListedForApp(String trackerId, int appUid) {
+ return trackersWhitelistByApp.getOrDefault(appUid, new HashSet()).contains(trackerId);
+ }
+
+ public boolean areWhiteListEmpty() {
+ boolean empty = true;
+ for (Set trackers: trackersWhitelistByApp.values()) {
+ empty = trackers.isEmpty();
+ }
+
+ return appsWhitelist.isEmpty() && empty;
+ }
+
+ public List getWhiteListedApp() {
+ return new ArrayList(appsWhitelist);
+ }
+
+ public List getWhiteListForApp(int appUid) {
+ return new ArrayList(trackersWhitelistByApp.getOrDefault(appUid, new HashSet()));
+ }
+}