diff options
author | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2022-08-17 08:49:05 +0000 |
---|---|---|
committer | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2022-08-17 08:49:05 +0000 |
commit | 1b9c519e073a5e64eeeafd747ddac75fde5f88df (patch) | |
tree | a767b29c62cb88ec39c3475ee579439a25141474 | |
parent | 12510a55c9c2b1d21c6e1f45d0058778ddfc9eaa (diff) | |
parent | c8d88ec3364218802bc48257b7766ad8f19a6e45 (diff) |
Merge branch '2-simplify_source_module_tree' into 'main'
2-Simplify sources modules tree
See merge request e/os/advanced-privacy!79
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())); + } +} |