summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/.gitignore1
-rw-r--r--api/build.gradle90
-rw-r--r--api/consumer-rules.pro0
-rw-r--r--api/proguard-rules.pro21
-rw-r--r--api/src/main/AndroidManifest.xml21
-rw-r--r--api/src/main/java/foundation/e/privacymodules/DependencyInjector.kt32
-rw-r--r--api/src/main/java/foundation/e/privacymodules/location/IFakeLocationModule.kt41
-rw-r--r--api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt158
-rw-r--r--api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt127
-rw-r--r--api/src/main/java/foundation/e/privacymodules/permissions/data/AppOpModes.kt43
-rw-r--r--api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt30
-rw-r--r--api/src/main/java/foundation/e/privacymodules/permissions/data/PermissionDescription.kt26
-rw-r--r--api/src/main/java/foundation/e/privacymodules/trackers/IBlockTrackersPrivacyModule.kt96
-rw-r--r--api/src/main/java/foundation/e/privacymodules/trackers/IDNSBlocker.kt26
-rw-r--r--api/src/main/java/foundation/e/privacymodules/trackers/ITrackTrackersPrivacyModule.kt96
-rw-r--r--api/src/main/java/foundation/e/privacymodules/trackers/Tracker.kt28
-rw-r--r--app/build.gradle98
-rw-r--r--app/src/google/res/values/strings.xml3
-rw-r--r--app/src/main/AndroidManifest.xml32
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt5
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt33
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt23
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt16
-rw-r--r--app/src/standalone/res/values-night/colors.xml28
-rw-r--r--app/src/standalone/res/values/colors.xml27
-rw-r--r--app/src/standalone/res/values/strings.xml3
-rw-r--r--build.gradle10
-rw-r--r--dependencies.gradle21
-rw-r--r--fakelocation/.gitignore1
-rw-r--r--fakelocation/build.gradle54
-rw-r--r--fakelocation/consumer-rules.pro0
-rw-r--r--fakelocation/fakelocationdemo/.gitignore1
-rw-r--r--fakelocation/fakelocationdemo/build.gradle71
-rw-r--r--fakelocation/fakelocationdemo/proguard-rules.pro21
-rw-r--r--fakelocation/fakelocationdemo/src/main/AndroidManifest.xml43
-rw-r--r--fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt209
-rw-r--r--fakelocation/fakelocationdemo/src/main/res/layout/activity_main.xml130
-rw-r--r--fakelocation/fakelocationdemo/src/main/res/values/colors.xml27
-rw-r--r--fakelocation/fakelocationdemo/src/main/res/values/strings.xml20
-rw-r--r--fakelocation/fakelocationdemo/src/main/res/values/themes.xml33
-rw-r--r--fakelocation/proguard-rules.pro21
-rw-r--r--fakelocation/src/main/AndroidManifest.xml32
-rw-r--r--fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/FakeLocationModule.kt117
-rw-r--r--fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/FakeLocationService.kt112
-rw-r--r--gradle/wrapper/gradle-wrapper.properties6
-rw-r--r--permissionsstandalone/.gitignore1
-rw-r--r--permissionsstandalone/build.gradle60
-rw-r--r--permissionsstandalone/consumer-rules.pro0
-rw-r--r--permissionsstandalone/proguard-rules.pro21
-rw-r--r--permissionsstandalone/src/main/AndroidManifest.xml23
-rw-r--r--permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt49
-rw-r--r--settings.gradle7
-rw-r--r--trackers/build.gradle45
-rw-r--r--trackers/src/main/AndroidManifest.xml37
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.java173
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.java86
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.java42
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.java83
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.java125
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.java150
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.java507
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.java95
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.java71
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.java153
64 files changed, 3671 insertions, 90 deletions
diff --git a/api/.gitignore b/api/.gitignore
new file mode 100644
index 0000000..42afabf
--- /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 0000000..b8ced30
--- /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 0000000..e69de29
--- /dev/null
+++ b/api/consumer-rules.pro
diff --git a/api/proguard-rules.pro b/api/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /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 0000000..937e285
--- /dev/null
+++ b/api/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 E FOUNDATION
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="foundation.e.privacymodules.api"
+ >
+</manifest> \ 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 0000000..bcf82d2
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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 0000000..ecad2a4
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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 0000000..68f7ee1
--- /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 <https://www.gnu.org/licenses/>.
+ */
+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<ApplicationDescription> {
+ val appInfos = context.packageManager.getInstalledApplications(0)
+ return appInfos.map { buildApplicationDescription(it, false) }
+ }
+
+ /**
+ * @see IPermissionsPrivacyModule.getInstalledApplications
+ */
+ override fun getInstalledApplications(): List<ApplicationDescription> {
+ 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<String> {
+ 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 0000000..ba85f13
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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<ApplicationDescription>
+
+ /**
+ * List all the installed application on the device.
+ * @return list of filled up [ApplicationDescription]
+ */
+ fun getAllApplications(): List<ApplicationDescription>
+
+ /**
+ * 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<String>
+
+ 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 0000000..367645d
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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 0000000..cafe256
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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 0000000..9ed297d
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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 0000000..53b540e
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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<Int>
+
+ /**
+ * 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<Tracker>
+
+ /**
+ * 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 0000000..a132aef
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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 0000000..139290e
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+package foundation.e.privacymodules.trackers
+
+/**
+ * Get reporting about trackers calls.
+ */
+interface ITrackTrackersPrivacyModule {
+
+ fun start(trackers: List<Tracker>, enableNotification: Boolean = true)
+
+ /**
+ * List all the trackers encountered for a specific app.
+ */
+ fun getTrackersForApp(appUid: Int): List<Tracker>
+
+ /**
+ * 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<Int, Int>
+
+ /**
+ * Return the number of encountered trackers for the last 24 hours
+ */
+ fun getPastDayTrackersCount(): Int
+
+ /**
+ * Return the number of encountered trackers for the last month
+ */
+ fun getPastMonthTrackersCount(): Int
+
+ /**
+ * Return the number of encountered trackers for the last year
+ */
+ fun getPastYearTrackersCount(): Int
+
+
+ /**
+ * Return number of trackers calls by hours, for the last 24hours.
+ * @return list of 24 numbers of trackers calls by hours
+ */
+ fun getPastDayTrackersCalls(): List<Pair<Int, Int>>
+
+ /**
+ * Return number of trackers calls by day, for the last 30 days.
+ * @return list of 30 numbers of trackers calls by day
+ */
+ fun getPastMonthTrackersCalls(): List<Pair<Int, Int>>
+
+ /**
+ * Return number of trackers calls by month, for the last 12 month.
+ * @return list of 12 numbers of trackers calls by month
+ */
+ fun getPastYearTrackersCalls(): List<Pair<Int, Int>>
+
+ fun getPastDayTrackersCallsByApps(): Map<Int, Pair<Int, Int>>
+
+ fun getPastDayTrackersCallsForApp(appUId: Int): Pair<Int, Int>
+
+ 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 0000000..0a4395a
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+package foundation.e.privacymodules.trackers
+
+/**
+ * Describe a tracker.
+ */
+data class Tracker(
+ val id: String,
+ val hostnames: Set<String>,
+ val label: String,
+ val exodusId: String?
+)
diff --git a/app/build.gradle b/app/build.gradle
index 5f2b302..61ee623 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 ebf51d0..0000000
--- a/app/src/google/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<resources>
- <string name="app_name">google - PrivacyModulesDemo</string>
-</resources>
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d285b6f..d2a824a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,4 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 ECORP
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="foundation.e.privacycentralapp"
@@ -33,6 +49,7 @@
>
<receiver
android:name=".common.BootCompletedReceiver"
+ android:exported="true"
>
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
@@ -53,19 +70,28 @@
android:resource="@xml/widget_info"
/>
</receiver>
- <receiver android:name=".widget.WidgetCommandReceiver">
+ <receiver android:name=".widget.WidgetCommandReceiver"
+ android:exported="true">
<intent-filter>
<action android:name="toggle_privacy" />
</intent-filter>
</receiver>
<activity android:name=".main.MainActivity"
- android:launchMode="singleTask">
+ android:launchMode="singleTask"
+ android:exported="true"
+ >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.INFO"/>
+ <category android:name="${mainActivityIntentFilterCategory}"/>
</intent-filter>
</activity>
+
+ <service
+ android:name="org.torproject.android.service.OrbotService"
+ android:exported="false"
+
+ />
</application>
</manifest> \ 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 6be3724..a44a00a 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 aa4276d..f7b5439 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<Float, Float>?, 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 2b858e9..d98cb5d 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 af20a72..afba3d0 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 0000000..079b968
--- /dev/null
+++ b/app/src/standalone/res/values-night/colors.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 E FOUNDATION
+ ~
+ ~ This program is free software: you can redistribute it and/or modify
+ ~ it under the terms of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<resources>
+ <!--themable -->
+ <color name="primary">#272727</color>
+ <color name="accent">#5DB2FF</color>
+
+ <color name="primary_text">#CCFFFFFF</color>
+ <color name="secondary_text">#8CFFFFFF</color>
+
+ <color name="background">#121212</color>
+
+</resources> \ 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 0000000..bd27922
--- /dev/null
+++ b/app/src/standalone/res/values/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 E FOUNDATION
+ ~
+ ~ This program is free software: you can redistribute it and/or modify
+ ~ it under the terms of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<resources>
+ <!--themable -->
+ <color name="primary">#FFFFFF</color>
+ <color name="accent">#0086FF</color>
+
+ <color name="primary_text">#CC000000</color>
+ <color name="secondary_text">#8C000000</color>
+
+ <color name="background">#FAFAFA</color>
+</resources> \ 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 0000000..7719e7b
--- /dev/null
+++ b/app/src/standalone/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+ <string name="app_name">A-P - Standalone</string>
+</resources>
diff --git a/build.gradle b/build.gradle
index 2442f01..5222057 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 dcb9f9d..ed329c7 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 0000000..42afabf
--- /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 0000000..ea28e44
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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 0000000..e69de29
--- /dev/null
+++ b/fakelocation/consumer-rules.pro
diff --git a/fakelocation/fakelocationdemo/.gitignore b/fakelocation/fakelocationdemo/.gitignore
new file mode 100644
index 0000000..42afabf
--- /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 0000000..12ed2e7
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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 0000000..481bb43
--- /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 0000000..202599a
--- /dev/null
+++ b/fakelocation/fakelocationdemo/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 E FOUNDATION
+ ~
+ ~ This program is free software: you can redistribute it and/or modify
+ ~ it under the terms of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="foundation.e.privacymodules.fakelocationdemo"
+ >
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+
+ <application
+ android:allowBackup="true"
+ android:label="@string/app_name"
+ android:theme="@style/Theme.PrivacyCentralApp"
+ >
+ <activity
+ android:exported="true"
+ android:name=".MainActivity"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest> \ 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 0000000..1b0a35b
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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 0000000..33fce69
--- /dev/null
+++ b/fakelocation/fakelocationdemo/src/main/res/layout/activity_main.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 E FOUNDATION
+ ~
+ ~ This program is free software: you can redistribute it and/or modify
+ ~ it under the terms of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ -->
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+ <data>
+ <variable name="granted" type="Boolean" />
+ <variable name="mockedLocation" type="String" />
+ <variable name="currentLocation" type="String" />
+ <variable name="providerInfo" type="String" />
+ <variable name="view" type="foundation.e.privacymodules.fakelocationdemo.MainActivity" />
+ </data>
+ <androidx.core.widget.NestedScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="24dp">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ >
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Fake location requires a specific permissions to work."
+ />
+ <androidx.appcompat.widget.SwitchCompat
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:checked="@{granted}"
+ android:onClick="@{view::onClickPermission}"
+ />
+ </LinearLayout>
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="True location"
+ android:onClick="@{view::onClickReset}"
+ />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="Paris"
+ android:onClick="@{view::onClickParis}"
+ />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="London"
+ android:onClick="@{view::onClickLondon}"
+ />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="Amsterdam"
+ android:onClick="@{view::onClickAmsterdam}"
+ />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ >
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Listen to location"
+ />
+ <androidx.appcompat.widget.SwitchCompat
+ android:id="@+id/toggle_listen_location"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:onClick="@{view::onClickToggleListenLocation}"
+ />
+ </LinearLayout>
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:text="Mocked location:"
+ android:textStyle="bold"
+ />
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:text="@{mockedLocation}"
+ />
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:text="Current Location:"
+ android:textStyle="bold"
+ />
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:text="@{currentLocation}"
+ />
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@{providerInfo}"
+ />
+ </LinearLayout>
+ </androidx.core.widget.NestedScrollView>
+</layout> \ 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 0000000..29591a8
--- /dev/null
+++ b/fakelocation/fakelocationdemo/src/main/res/values/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 E FOUNDATION
+ ~
+ ~ This program is free software: you can redistribute it and/or modify
+ ~ it under the terms of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<resources>
+ <color name="purple_200">#FFBB86FC</color>
+ <color name="purple_500">#FF6200EE</color>
+ <color name="purple_700">#FF3700B3</color>
+ <color name="teal_200">#FF03DAC5</color>
+ <color name="teal_700">#FF018786</color>
+ <color name="black">#FF000000</color>
+ <color name="white">#FFFFFFFF</color>
+</resources> \ 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 0000000..17b69db
--- /dev/null
+++ b/fakelocation/fakelocationdemo/src/main/res/values/strings.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright (C) 2022 E FOUNDATION
+ ~
+ ~ This program is free software: you can redistribute it and/or modify
+ ~ it under the terms of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<resources>
+ <string name="app_name">FakeLocationDemo</string>
+</resources> \ 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 0000000..9ce6d28
--- /dev/null
+++ b/fakelocation/fakelocationdemo/src/main/res/values/themes.xml
@@ -0,0 +1,33 @@
+<!--
+ ~ Copyright (C) 2022 E FOUNDATION
+ ~
+ ~ This program is free software: you can redistribute it and/or modify
+ ~ it under the terms of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- Base application theme. -->
+ <style name="Theme.PrivacyCentralApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+ <!-- Primary brand color. -->
+ <item name="colorPrimary">@color/purple_500</item>
+ <item name="colorPrimaryVariant">@color/purple_700</item>
+ <item name="colorOnPrimary">@color/white</item>
+ <!-- Secondary brand color. -->
+ <item name="colorSecondary">@color/teal_200</item>
+ <item name="colorSecondaryVariant">@color/teal_700</item>
+ <item name="colorOnSecondary">@color/black</item>
+ <!-- Status bar color. -->
+ <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+ <!-- Customize your theme here. -->
+ </style>
+</resources> \ No newline at end of file
diff --git a/fakelocation/proguard-rules.pro b/fakelocation/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /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 0000000..5077c24
--- /dev/null
+++ b/fakelocation/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 E FOUNDATION
+ ~
+ ~ This program is free software: you can redistribute it and/or modify
+ ~ it under the terms of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="foundation.e.privacymodules.fakelocation"
+ >
+
+ <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"
+ tools:ignore="MockLocation,ProtectedPermissions" />
+
+ <application>
+ <service android:name="foundation.e.privacymodules.fakelocation.FakeLocationService"
+ android:enabled="true" />
+ </application>
+</manifest> \ 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 0000000..43a4545
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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 0000000..1337ddd
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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<Double, Double>? = 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 dc62e2d..35be582 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 0000000..42afabf
--- /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 0000000..aadb84f
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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 0000000..e69de29
--- /dev/null
+++ b/permissionsstandalone/consumer-rules.pro
diff --git a/permissionsstandalone/proguard-rules.pro b/permissionsstandalone/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /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 0000000..662ea44
--- /dev/null
+++ b/permissionsstandalone/src/main/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 E FOUNDATION
+ ~
+ ~ This program is free software: you can redistribute it and/or modify
+ ~ it under the terms of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="foundation.e.privacymodules.permissionsstandalone"
+ >
+
+</manifest> \ 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 0000000..d32cada
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+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 e39b561..24b6eef 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 0000000..51f8448
--- /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 0000000..debdf61
--- /dev/null
+++ b/trackers/src/main/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 ECORP
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="foundation.e.privacymodules.trackers">
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+
+
+ <application>
+ <service
+ android:name="foundation.e.privacymodules.trackers.DNSBlockerService"
+ android:enabled="true"
+ android:exported="true" />
+ </application>
+
+</manifest> \ 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 0000000..80f00c1
--- /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 0000000..6250621
--- /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 0000000..1563163
--- /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 0000000..3710253
--- /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<DetectedTracker> queue;
+ private boolean stopped = false;
+
+
+ public TrackersLogger(Context context) {
+ statsRepository = StatsRepository.getInstance(context);
+ queue = new LinkedBlockingQueue<DetectedTracker>();
+ 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 0000000..ea62766
--- /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<Listener> 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<Tracker> getWhiteList(int appUid) {
+ List<Tracker> trackers = new ArrayList();
+ for (String trackerId: whitelistRepository.getWhiteListForApp(appUid)) {
+ trackers.add(trackersRepository.getTracker(trackerId));
+ }
+ return trackers;
+ }
+
+ @Override
+ public List<Integer> 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 0000000..38b2c8f
--- /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<ITrackTrackersPrivacyModule.Listener> 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<Tracker> 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<Pair<Integer, Integer>> getPastDayTrackersCalls() {
+ return statsRepository.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS);
+ }
+
+ @Override
+ public List<Pair<Integer, Integer>> getPastMonthTrackersCalls() {
+ return statsRepository.getTrackersCallsOnPeriod(30, ChronoUnit.DAYS);
+ }
+
+ @Override
+ public List<Pair<Integer, Integer>> getPastYearTrackersCalls() {
+ return statsRepository.getTrackersCallsOnPeriod(12, ChronoUnit.MONTHS);
+ }
+
+ @Override
+ public int getTrackersCount() {
+ return statsRepository.getContactedTrackersCount();
+ }
+
+ @Override
+ public Map<Integer, Integer> getTrackersCountByApp() {
+ return statsRepository.getContactedTrackersCountByApp();
+ }
+
+ @Override
+ public List<Tracker> 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<Integer, Pair<Integer, Integer>> getPastDayTrackersCallsByApps() {
+ return statsRepository.getCallsByApps(24, ChronoUnit.HOURS);
+ }
+
+ @NotNull
+ @Override
+ public Pair<Integer, Integer> 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 0000000..0650114
--- /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<String, Pair<Integer, Integer>> 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<String, Pair<Integer, Integer>> 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<Pair<Integer, Integer>> callsByPeriodToPeriodsList(
+ Map<String, Pair<Integer, Integer>> callsByPeriod,
+ int periodsCount,
+ TemporalUnit periodUnit,
+ String javaPeriodFormat
+ ) {
+ ZonedDateTime currentDate = ZonedDateTime.now().minus(periodsCount, periodUnit);
+ DateTimeFormatter formater = DateTimeFormatter.ofPattern(javaPeriodFormat);
+
+ List<Pair<Integer, Integer>> 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<Pair<Integer, Integer>> 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<String, Pair<Integer, Integer>> 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Pair<Integer, Integer>> 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<Integer, Pair<Integer, Integer>> 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<Integer, Integer> 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<Integer, Pair<Integer, Integer>> callsByApp = new HashMap<>();
+
+ Pair<Integer, Integer> 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<Tracker> 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<Tracker> 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<Tracker> 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 0000000..bfe688f
--- /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<Boolean> 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<Boolean> 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<Pair<Integer, Integer>> getTrackersCallsOnPeriod(int periodsCount, TemporalUnit periodUnit) {
+ return database.getTrackersCallsOnPeriod(periodsCount, periodUnit);
+ }
+
+ public int getActiveTrackersByPeriod(int periodsCount, TemporalUnit periodUnit) {
+ return database.getActiveTrackersByPeriod(periodsCount, periodUnit);
+ }
+
+ public Map<Integer, Integer> getContactedTrackersCountByApp() {
+ return database.getContactedTrackersCountByApp();
+ }
+
+ public int getContactedTrackersCount() {
+ return database.getContactedTrackersCount();
+ }
+
+ public List<Tracker> getAllTrackersOfApp(int app_uid) {
+ return database.getAllTrackersOfApp(app_uid);
+ }
+
+ public Map<Integer, Pair<Integer, Integer>> getCallsByApps(int periodCount, TemporalUnit periodUnit) {
+ return database.getCallsByApps(periodCount, periodUnit);
+ }
+
+ public Pair<Integer, Integer> 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 0000000..5c77c7a
--- /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<String, Tracker> trackersById = new HashMap();
+ private Map<String, String> hostnameToId = new HashMap();
+
+ public void setTrackersList(List<Tracker> list) {
+ Map<String, Tracker> trackersById = new HashMap();
+ Map<String, String> 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 0000000..9bfca7f
--- /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<Integer> appsWhitelist;
+ private Map<Integer, Set<String>> 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<Integer> appWhiteList = new HashSet();
+ for (String appUid: prefs.getStringSet(KEY_APPS_WHITELIST, new HashSet<String>())) {
+ 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<String>()));
+ }
+
+ 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<String> current = new HashSet(prefs.getStringSet(KEY_APPS_WHITELIST, new HashSet<String>()));
+ 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<String> trackers;
+ if (trackersWhitelistByApp.containsKey(appUid)) {
+ trackers = trackersWhitelistByApp.get(appUid);
+ } else {
+ trackers = new HashSet<String>();
+ 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<String> trackers: trackersWhitelistByApp.values()) {
+ empty = trackers.isEmpty();
+ }
+
+ return appsWhitelist.isEmpty() && empty;
+ }
+
+ public List<Integer> getWhiteListedApp() {
+ return new ArrayList(appsWhitelist);
+ }
+
+ public List<String> getWhiteListForApp(int appUid) {
+ return new ArrayList(trackersWhitelistByApp.getOrDefault(appUid, new HashSet()));
+ }
+}