diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..42afabfd2abebf31384ca7797186a27a4b7dbee8 --- /dev/null +++ b/api/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..b8ced30765f39c78d638ae09aec3ee3a6e78e394 --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,90 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' + +android { + compileSdkVersion buildConfig.compileSdk + + defaultConfig { + minSdkVersion buildConfig.minSdk + targetSdkVersion buildConfig.targetSdk + + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation( + Libs.Kotlin.stdlib, + Libs.AndroidX.coreKtxAPI29, + Libs.Coroutines.core + ) +} + +//url "https://gitlab.e.foundation/api/v4/groups/e/privacy-central/-/packages/maven" + +publishing { + publications { + maven(MavenPublication) { + groupId 'foundation.e' + //You can either define these here or get them from project conf elsewhere + artifactId 'privacymodule.api' + version buildConfig.version.name + artifact "$buildDir/outputs/aar/api-release.aar" + //aar artifact you want to publish + + //generate pom nodes for dependencies + pom.withXml { + def dependenciesNode = asNode().appendNode('dependencies') + configurations.implementation.allDependencies.each { dependency -> + if (dependency.name != 'unspecified') { + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', dependency.group) + dependencyNode.appendNode('artifactId', dependency.name) + dependencyNode.appendNode('version', dependency.version) + } + } + } + repositories { + def ciJobToken = System.getenv("CI_JOB_TOKEN") + def ciApiV4Url = System.getenv("CI_API_V4_URL") + if (ciJobToken != null) { + maven { + url "${ciApiV4Url}/projects/900/packages/maven" + credentials(HttpHeaderCredentials) { + name = 'Job-Token' + value = ciJobToken + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + maven { + url "https://gitlab.e.foundation/api/v4/projects/900/packages/maven" + credentials(HttpHeaderCredentials) { + name = "Private-Token" + value = gitLabPrivateToken + // the variable resides in ~/.gradle/gradle.properties + } + authentication { + header(HttpHeaderAuthentication) + } + } + } + } + } + } +} diff --git a/api/consumer-rules.pro b/api/consumer-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/api/proguard-rules.pro b/api/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce --- /dev/null +++ b/api/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/api/src/main/AndroidManifest.xml b/api/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..937e285dfaf5b2126d23b627ceb1d2f22a7ded9e --- /dev/null +++ b/api/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/api/src/main/java/foundation/e/privacymodules/DependencyInjector.kt b/api/src/main/java/foundation/e/privacymodules/DependencyInjector.kt new file mode 100644 index 0000000000000000000000000000000000000000..bcf82d2c511094710dbae2c58f7474741251ad82 --- /dev/null +++ b/api/src/main/java/foundation/e/privacymodules/DependencyInjector.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacymodules + +import foundation.e.privacymodules.trackers.IDNSBlocker + +object DependencyInjector { + fun initialize( + dnsBlocker: IDNSBlocker + ) { + this.dnsBlocker = dnsBlocker + } + + + lateinit var dnsBlocker: IDNSBlocker + private set +} \ No newline at end of file diff --git a/api/src/main/java/foundation/e/privacymodules/location/IFakeLocationModule.kt b/api/src/main/java/foundation/e/privacymodules/location/IFakeLocationModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..ecad2a48f937632305f1ef576b6a2a0992875eb9 --- /dev/null +++ b/api/src/main/java/foundation/e/privacymodules/location/IFakeLocationModule.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacymodules.location + +/** + * Manage a fake location on the device. + */ +interface IFakeLocationModule { + /** + * Start to fake the location module. Call [setFakeLocation] after to set the fake + * position. + */ + fun startFakeLocation() + + /** + * Set or update the faked position. + * @param latitude the latitude of the fake position in degrees. + * @param longitude the longitude of the fake position in degrees. + */ + fun setFakeLocation(latitude: Double, longitude: Double) + + /** + * Stop the fake location module, giving back hand to the true location modules. + */ + fun stopFakeLocation() +} diff --git a/api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt b/api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..68f7ee1d572f46e3ca212c82dbdbfa48d188664a --- /dev/null +++ b/api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package foundation.e.privacymodules.permissions + +import android.app.AppOpsManager +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.PermissionInfo +import android.content.pm.PermissionInfo.PROTECTION_DANGEROUS +import android.graphics.drawable.Drawable +import android.os.Build +import android.util.Log +import foundation.e.privacymodules.permissions.data.AppOpModes +import foundation.e.privacymodules.permissions.data.ApplicationDescription +import foundation.e.privacymodules.permissions.data.PermissionDescription + +/** + * Implementation of the commons functionality between privileged and standard + * versions of the module. + * @param context an Android context, to retrieve packageManager for example. + */ +abstract class APermissionsPrivacyModule(protected val context: Context): IPermissionsPrivacyModule { + + companion object { + private const val TAG = "PermissionsModule" + } + /** + * @see IPermissionsPrivacyModule.getAllApplications + */ + override fun getAllApplications(): List { + val appInfos = context.packageManager.getInstalledApplications(0) + return appInfos.map { buildApplicationDescription(it, false) } + } + + /** + * @see IPermissionsPrivacyModule.getInstalledApplications + */ + override fun getInstalledApplications(): List { + return context.packageManager.getInstalledApplications(0) + .filter { it.flags and ApplicationInfo.FLAG_SYSTEM == 0 } + .map { buildApplicationDescription(it, false) } + } + + /** + * @see IPermissionsPrivacyModule.getInstalledApplications + */ + override fun getApplicationDescription(packageName: String): ApplicationDescription { + val appDesc = buildApplicationDescription(context.packageManager.getApplicationInfo(packageName, 0), false) + appDesc.icon = getApplicationIcon(appDesc.packageName) + return appDesc + } + + /** + * * @see IPermissionsPrivacyModule.getPermissions + */ + override fun getPermissions(packageName: String): List { + val packageInfo = context.packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS) + return packageInfo.requestedPermissions?.asList() ?: emptyList() + } + + override fun getPermissionDescription(permissionName: String): PermissionDescription { + val info = context.packageManager.getPermissionInfo(permissionName, 0) + return PermissionDescription( + name = permissionName, + isDangerous = isPermissionsDangerous(info), + group = null, + label = info.loadLabel(context.packageManager), + description = info.loadDescription(context.packageManager) + ) + } + + /** + * @see IPermissionsPrivacyModule.isDangerousPermissionGranted + */ + override fun isDangerousPermissionGranted(packageName: String, permissionName: String): Boolean { + return context.packageManager + .checkPermission(permissionName, packageName) == PackageManager.PERMISSION_GRANTED + } + + // on google version, work only for the current package. + @Suppress("DEPRECATION") + override fun getAppOpMode( + appDesc: ApplicationDescription, + appOpPermissionName: String + ): AppOpModes { + + val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager + + val mode = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + appOps.checkOpNoThrow(appOpPermissionName, + + appDesc.uid, appDesc.packageName) + } else { + appOps.unsafeCheckOpNoThrow( + appOpPermissionName, + appDesc.uid, appDesc.packageName) + } + + return AppOpModes.getByModeValue(mode) + } + + override fun isPermissionsDangerous(permissionName: String): Boolean { + try { + val permissionInfo = context.packageManager.getPermissionInfo(permissionName, 0) + return isPermissionsDangerous(permissionInfo) + } catch (e: Exception) { + Log.w(TAG, "exception in isPermissionsDangerous(String)", e) + return false + } + } + + @Suppress("DEPRECATION") + private fun isPermissionsDangerous(permissionInfo: PermissionInfo): Boolean { + try { + return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + permissionInfo.protectionLevel and PROTECTION_DANGEROUS == 1 + } else { + permissionInfo.protection == PROTECTION_DANGEROUS + } + } catch (e: Exception) { + Log.w(TAG, "exception in isPermissionsDangerous(PermissionInfo)", e) + return false + } + } + + override fun buildApplicationDescription(appInfo: ApplicationInfo, withIcon: Boolean) + : ApplicationDescription { + return ApplicationDescription( + packageName = appInfo.packageName, + uid = appInfo.uid, + label = getAppLabel(appInfo), + icon = if (withIcon) getApplicationIcon(appInfo.packageName) else null + ) + } + + private fun getAppLabel(appInfo: ApplicationInfo): CharSequence { + return context.packageManager.getApplicationLabel(appInfo) + } + + override fun getApplicationIcon(packageName: String): Drawable? { + return context.packageManager.getApplicationIcon(packageName) + } +} diff --git a/api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt b/api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..ba85f1336c649418df0efebd6104387a204b3e83 --- /dev/null +++ b/api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacymodules.permissions + +import android.content.pm.ApplicationInfo +import android.graphics.drawable.Drawable +import foundation.e.privacymodules.permissions.data.AppOpModes +import foundation.e.privacymodules.permissions.data.ApplicationDescription +import foundation.e.privacymodules.permissions.data.PermissionDescription + +/** + * List applications and manage theirs permissions. + */ +interface IPermissionsPrivacyModule { + + fun buildApplicationDescription(appInfo: ApplicationInfo, withIcon: Boolean = true): ApplicationDescription + + /** + * List the installed application on the device which have not the FLAGS_SYSTEM. + * @return list of filled up [ApplicationDescription] + */ + fun getInstalledApplications(): List + + /** + * List all the installed application on the device. + * @return list of filled up [ApplicationDescription] + */ + fun getAllApplications(): List + + /** + * List of permissions names used by an app, specified by its [packageName]. + * @param packageName the appId of the app + * @return the list off permission, in the "android.permission.PERMISSION" format. + */ + fun getPermissions(packageName: String): List + + fun getPermissionDescription(permissionName: String): PermissionDescription + + + /** + * Get the filled up [ApplicationDescription] for the app specified by its [packageName] + * @param packageName the appId of the app + * @return the informations about the app. + */ + fun getApplicationDescription(packageName: String): ApplicationDescription + + /** + * Check if the current runtime permission is granted for the specified app. + * + * @param packageName the packageName of the app + * @param permissionName the name of the permission in "android.permission.PERMISSION" format. + * @return the current status for this permission. + */ + fun isDangerousPermissionGranted(packageName: String, permissionName: String): Boolean + + + /** + * Get the appOps mode for the specified [appOpPermissionName] of the specified application. + * + * @param appDesc the application + * @param appOpPermissionName the AppOps permission name. + * @return mode, as a [AppOpModes] + */ + fun getAppOpMode(appDesc: ApplicationDescription, appOpPermissionName: String): AppOpModes + + /** + * Grant or revoke the specified permission for the specigfied app. + * If their is not enough privileges to get the permission, return the false + * + * @param appDesc the application + * @param permissionName the name of the permission in "android.permission.PERMISSION" format. + * @param grant true grant the permission, false revoke it. + * @return true if the permission is or has just been granted, false if + * user has to do it himself. + */ + fun toggleDangerousPermission( + appDesc: ApplicationDescription, + permissionName: String, + grant: Boolean + ): Boolean + + + /** + * Change the appOp Mode for the specified appOpPermission and application. + * @param appDesc the application + * @param appOpPermissionName the AppOps permission name. + * @return true if the mode has been changed, false if + * user has to do it himself. + */ + fun setAppOpMode( + appDesc: ApplicationDescription, + appOpPermissionName: String, + status: AppOpModes + ): Boolean + + /** + * Return true if the application is flagged Dangerous. + */ + fun isPermissionsDangerous(permissionName: String): Boolean + + /** + * Get the application icon. + */ + fun getApplicationIcon(packageName: String): Drawable? + + /** + * Authorize the specified package to be used as Vpn. + * @return true if authorization has been set, false if an error has occurred. + */ + fun setVpnPackageAuthorization(packageName: String): Boolean + +} \ No newline at end of file diff --git a/api/src/main/java/foundation/e/privacymodules/permissions/data/AppOpModes.kt b/api/src/main/java/foundation/e/privacymodules/permissions/data/AppOpModes.kt new file mode 100644 index 0000000000000000000000000000000000000000..367645dd0e322c9bc4d8a0548ead10ecc687d448 --- /dev/null +++ b/api/src/main/java/foundation/e/privacymodules/permissions/data/AppOpModes.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacymodules.permissions.data + +import android.app.AppOpsManager.* +import android.os.Build + +enum class AppOpModes(val modeValue: Int) { + ALLOWED(MODE_ALLOWED), + IGNORED(MODE_IGNORED), + ERRORED(MODE_ERRORED), + DEFAULT(MODE_DEFAULT), + FOREGROUND(if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) MODE_ALLOWED else MODE_FOREGROUND); + + companion object { + private val byMode = mapOf( + FOREGROUND.modeValue to FOREGROUND, + ALLOWED.modeValue to ALLOWED, + IGNORED.modeValue to IGNORED, + ERRORED.modeValue to ERRORED, + DEFAULT.modeValue to DEFAULT, + ) + + fun getByModeValue(modeValue: Int): AppOpModes { + return byMode.get(modeValue) ?: DEFAULT + } + } +} diff --git a/api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt b/api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt new file mode 100644 index 0000000000000000000000000000000000000000..cafe256964009c51d1cf07ff72483cc41d1297c7 --- /dev/null +++ b/api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacymodules.permissions.data + +import android.graphics.drawable.Drawable + +/** + * Useful informations to identify and describe an application. + */ +data class ApplicationDescription( + val packageName: String, + val uid: Int, + var label: CharSequence?, + var icon: Drawable? +) diff --git a/api/src/main/java/foundation/e/privacymodules/permissions/data/PermissionDescription.kt b/api/src/main/java/foundation/e/privacymodules/permissions/data/PermissionDescription.kt new file mode 100644 index 0000000000000000000000000000000000000000..9ed297d3283522a066e89461a398929e2fa405b0 --- /dev/null +++ b/api/src/main/java/foundation/e/privacymodules/permissions/data/PermissionDescription.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacymodules.permissions.data + +data class PermissionDescription( + val name: String, + var isDangerous: Boolean, + val group: String?, + var label: CharSequence?, + var description: CharSequence? +) \ No newline at end of file diff --git a/api/src/main/java/foundation/e/privacymodules/trackers/IBlockTrackersPrivacyModule.kt b/api/src/main/java/foundation/e/privacymodules/trackers/IBlockTrackersPrivacyModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..53b540e87f7bf371f6a3f6f12a40761d58d38ae6 --- /dev/null +++ b/api/src/main/java/foundation/e/privacymodules/trackers/IBlockTrackersPrivacyModule.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacymodules.trackers + + +/** + * Manage trackers blocking and whitelisting. + */ +interface IBlockTrackersPrivacyModule { + + + /** + * Get the state of the blockin module + * @return true when blocking is enabled, false otherwise. + */ + fun isBlockingEnabled(): Boolean + + /** + * Enable blocking, using the previously configured whitelists + */ + fun enableBlocking() + + /** + * Disable blocking + */ + fun disableBlocking() + + /** + * Set or unset in whitelist the App with the specified uid. + * @param appUid the uid of the app + * @param isWhiteListed true, the app will appears in whitelist, false, it won't + */ + fun setWhiteListed(appUid: Int, isWhiteListed: Boolean) + + /** + * Set or unset in whitelist the specifid tracked, for the App specified by its uid. + * @param tracker the tracker + * @param appUid the uid of the app + * @param isWhiteListed true, the app will appears in whitelist, false, it won't + */ + fun setWhiteListed(tracker: Tracker, appUid: Int, isWhiteListed: Boolean) + + /** + * Return true if nothing has been added to the whitelist : everything is blocked. + */ + fun isWhiteListEmpty(): Boolean + + /** + * Return the white listed App, by their UID + */ + fun getWhiteListedApp(): List + + /** + * Return true if the App is whitelisted for trackers blocking. + */ + fun isWhitelisted(appUid: Int): Boolean + + + /** + * List the white listed trackers for an App specified by it uid + */ + fun getWhiteList(appUid: Int): List + + /** + * Callback interface to get updates about the state of the Block trackers module. + */ + interface Listener { + + /** + * Called when the trackers blocking is activated or deactivated. + * @param isBlocking true when activated, false otherwise. + */ + fun onBlockingToggle(isBlocking: Boolean) + } + + fun addListener(listener: Listener) + + fun removeListener(listener: Listener) + + fun clearListeners() +} diff --git a/api/src/main/java/foundation/e/privacymodules/trackers/IDNSBlocker.kt b/api/src/main/java/foundation/e/privacymodules/trackers/IDNSBlocker.kt new file mode 100644 index 0000000000000000000000000000000000000000..a132aefa7fc1492a639a97c07fc8e4128eae5dbd --- /dev/null +++ b/api/src/main/java/foundation/e/privacymodules/trackers/IDNSBlocker.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacymodules.trackers + +interface IDNSBlocker { + companion object { + const val DUMMY_APP_UID = -1 + } + + fun shouldBlock(hostname: String, appUid: Int): Boolean +} diff --git a/api/src/main/java/foundation/e/privacymodules/trackers/ITrackTrackersPrivacyModule.kt b/api/src/main/java/foundation/e/privacymodules/trackers/ITrackTrackersPrivacyModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..139290e8c974ac1de7b0140a2c25236803051c41 --- /dev/null +++ b/api/src/main/java/foundation/e/privacymodules/trackers/ITrackTrackersPrivacyModule.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacymodules.trackers + +/** + * Get reporting about trackers calls. + */ +interface ITrackTrackersPrivacyModule { + + fun start(trackers: List, enableNotification: Boolean = true) + + /** + * List all the trackers encountered for a specific app. + */ + fun getTrackersForApp(appUid: Int): List + + /** + * Return the number of encountered trackers since "ever" + */ + fun getTrackersCount(): Int + + /** + * Return the number of encountere trackers since "ever", for each app uid. + */ + fun getTrackersCountByApp(): Map + + /** + * Return the number of encountered trackers for the last 24 hours + */ + fun getPastDayTrackersCount(): Int + + /** + * Return the number of encountered trackers for the last month + */ + fun getPastMonthTrackersCount(): Int + + /** + * Return the number of encountered trackers for the last year + */ + fun getPastYearTrackersCount(): Int + + + /** + * Return number of trackers calls by hours, for the last 24hours. + * @return list of 24 numbers of trackers calls by hours + */ + fun getPastDayTrackersCalls(): List> + + /** + * Return number of trackers calls by day, for the last 30 days. + * @return list of 30 numbers of trackers calls by day + */ + fun getPastMonthTrackersCalls(): List> + + /** + * Return number of trackers calls by month, for the last 12 month. + * @return list of 12 numbers of trackers calls by month + */ + fun getPastYearTrackersCalls(): List> + + fun getPastDayTrackersCallsByApps(): Map> + + fun getPastDayTrackersCallsForApp(appUId: Int): Pair + + fun getPastDayMostLeakedApp(): Int + + interface Listener { + + /** + * Called when a new tracker attempt is logged. Consumer may choose to call other methods + * to refresh the data. + */ + fun onNewData() + } + + fun addListener(listener: Listener) + + fun removeListener(listener: Listener) + + fun clearListeners() +} \ No newline at end of file diff --git a/api/src/main/java/foundation/e/privacymodules/trackers/Tracker.kt b/api/src/main/java/foundation/e/privacymodules/trackers/Tracker.kt new file mode 100644 index 0000000000000000000000000000000000000000..0a4395ad5fd12032762f087ae463bdf6b49e096d --- /dev/null +++ b/api/src/main/java/foundation/e/privacymodules/trackers/Tracker.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacymodules.trackers + +/** + * Describe a tracker. + */ +data class Tracker( + val id: String, + val hostnames: Set, + val label: String, + val exodusId: String? +) diff --git a/app/build.gradle b/app/build.gradle index 5f2b3029544df83d9852c034c69497dc43d6c532..61ee623f8732de0b84823b8003468ff469dcdac1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,7 +16,10 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - manifestPlaceholders = [ persistent: "false" ] + manifestPlaceholders = [ + persistent: "false", + mainActivityIntentFilterCategory: "android.intent.category.LAUNCHER" + ] resValue("string", "mapbox_key", MAPBOX_KEY) } @@ -47,24 +50,36 @@ android { dimension 'os' minSdkVersion 29 targetSdkVersion 29 + signingConfig signingConfigs.eDebug } e30 { dimension 'os' minSdkVersion 30 targetSdkVersion 30 + signingConfig signingConfigs.eDebug + } + standalone { + dimension 'os' + applicationIdSuffix '.standalone' + minSdkVersion 26 + targetSdkVersion 31 + manifestPlaceholders = [ + persistent: "false", + mainActivityIntentFilterCategory: "android.intent.category.LAUNCHER" + ] + signingConfig signingConfigs.debug } -// google { -// applicationIdSuffix '.google' -// dimension 'os' -// } } buildTypes { debug { - signingConfig null // Set signing config to null as we use signingConfig per variant. + signingConfig null // Set signing config to null as we use signingConfig per variant. } release { - manifestPlaceholders = [ persistent: "true" ] + manifestPlaceholders = [ + persistent: "true", + mainActivityIntentFilterCategory: "android.intent.category.INFO" + ] minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' @@ -78,13 +93,6 @@ android { variant.outputs.all { output -> outputFileName = "Advanced_Privacy-${variant.versionName}-${variant.getFlavorName()}-${variant.buildType.name}.apk" } - if (variant.buildType.name == "debug") { - if (variant.getFlavorName() == "e29" || variant.getFlavorName() == "e30") { - variant.mergedFlavor.signingConfig = signingConfigs.eDebug - } else { - variant.mergedFlavor.signingConfig = signingConfigs.debug - } - } } compileOptions { @@ -103,53 +111,51 @@ android { } dependencies { + implementation project(':api') - compileOnly files('libs/e-ui-sdk-1.0.1-q.jar') - implementation files('libs/lineage-sdk.jar') - // include the google specific version of the modules, just for the google flavor - //googleImplementation project(":privacymodulesgoogle") - // include the e specific version of the modules, just for the e flavor - - implementation 'foundation.e:privacymodule.trackerfilter:0.7.0' - implementation 'foundation.e:privacymodule.api:1.1.0' - e29Implementation 'foundation.e:privacymodule.e-29:0.4.3' - e30Implementation 'foundation.e:privacymodule.e-30:0.4.3' - implementation 'foundation.e:privacymodule.tor:0.2.4' - + standaloneImplementation project(':permissionsstandalone') + e29Implementation('foundation.e:privacymodule.e-29:1.2.0') { + exclude group: 'foundation.e', module: 'privacymodule.api' + } + e30Implementation('foundation.e:privacymodule.e-30:1.2.0') { + exclude group: 'foundation.e', module: 'privacymodule.api' + } - // implementation Libs.Kotlin.stdlib - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$Versions.kotlin" -// implementation Libs.AndroidX.coreKtx - implementation "androidx.core:core-ktx:1.8.0" - -// implementation Libs.AndroidX.Fragment.fragmentKtx - implementation "androidx.fragment:fragment-ktx:$Versions.fragment" + implementation project(':fakelocation') - implementation 'androidx.appcompat:appcompat:1.4.2' -// implementation Libs.AndroidX.Lifecycle.runtime - implementation "androidx.lifecycle:lifecycle-runtime-ktx:$Versions.lifecycle" -// implementation Libs.AndroidX.Lifecycle.viewmodel - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$Versions.lifecycle" + e29CompileOnly files('libs/e-ui-sdk-1.0.1-q.jar') + e29Implementation files('libs/lineage-sdk.jar') - implementation 'androidx.work:work-runtime-ktx:2.7.1' + e30CompileOnly files('libs/e-ui-sdk-1.0.1-q.jar') + e30Implementation files('libs/lineage-sdk.jar') - implementation 'com.google.android.material:material:1.6.1' + implementation project(':trackers') + implementation 'foundation.e:privacymodule.tor:0.2.4' - implementation 'com.squareup.retrofit2:retrofit:2.9.0' - implementation 'com.squareup.retrofit2:converter-scalars:2.9.0' + implementation ( + Libs.Kotlin.stdlib, + Libs.AndroidX.coreKtx, + Libs.AndroidX.appCompat, + Libs.AndroidX.Fragment.fragmentKtx, + Libs.AndroidX.Lifecycle.runtime, + Libs.AndroidX.Lifecycle.viewmodel, + Libs.AndroidX.work, + Libs.material, -// implementation Libs.MapBox.sdk - implementation "com.mapbox.mapboxsdk:mapbox-android-sdk:$Versions.mapbox" - implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' + Libs.Retrofit.retrofit, + Libs.Retrofit.scalars, + Libs.MapBox.sdk, + Libs.mpAndroidCharts + ) + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' } static def log(Object val) { diff --git a/app/src/google/res/values/strings.xml b/app/src/google/res/values/strings.xml deleted file mode 100644 index ebf51d065e4e754c9819409f4e3ebc446cc04484..0000000000000000000000000000000000000000 --- a/app/src/google/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - google - PrivacyModulesDemo - diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d285b6f8f473a4e765eb78e2f06d47d13c6359b5..d2a824a13d4b30c53cbd4816e634a3e98664fa33 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,4 +1,20 @@ + @@ -53,19 +70,28 @@ android:resource="@xml/widget_info" /> - + + android:launchMode="singleTask" + android:exported="true" + > - + + + \ No newline at end of file diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index 6be3724b7bc61a59ab6f4ce8b0d9e5d81fb6063b..a44a00acf84db9472f020188d5cbeeec1b958abf 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -40,16 +40,15 @@ import foundation.e.privacycentralapp.features.location.FakeLocationViewModel import foundation.e.privacycentralapp.features.trackers.TrackersViewModel import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersFragment import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersViewModel +import foundation.e.privacymodules.fakelocation.FakeLocationModule import foundation.e.privacymodules.ipscrambler.IpScramblerModule import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule -import foundation.e.privacymodules.location.FakeLocationModule import foundation.e.privacymodules.location.IFakeLocationModule import foundation.e.privacymodules.permissions.PermissionsPrivacyModule import foundation.e.privacymodules.permissions.data.ApplicationDescription import foundation.e.privacymodules.trackers.api.BlockTrackersPrivacyModule import foundation.e.privacymodules.trackers.api.TrackTrackersPrivacyModule import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.GlobalScope /** @@ -61,7 +60,7 @@ class DependencyContainer(val app: Application) { val context: Context by lazy { app.applicationContext } // Drivers - private val fakeLocationModule: IFakeLocationModule by lazy { FakeLocationModule(app.applicationContext) } + private val fakeLocationModule: FakeLocationModule by lazy { FakeLocationModule(app.applicationContext) } private val permissionsModule by lazy { PermissionsPrivacyModule(app.applicationContext) } private val ipScramblerModule: IIpScramblerModule by lazy { IpScramblerModule(app.applicationContext) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt index aa4276d8d3db8ce20e780f9516c4c06fddac6694..f7b54393bd115bd00a3e4628397640e04b1ac13a 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt @@ -28,7 +28,7 @@ import android.util.Log import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.domain.entities.LocationMode import foundation.e.privacycentralapp.dummy.CityDataSource -import foundation.e.privacymodules.location.IFakeLocationModule +import foundation.e.privacymodules.fakelocation.FakeLocationModule import foundation.e.privacymodules.permissions.PermissionsPrivacyModule import foundation.e.privacymodules.permissions.data.AppOpModes import foundation.e.privacymodules.permissions.data.ApplicationDescription @@ -39,7 +39,7 @@ import kotlinx.coroutines.launch import kotlin.random.Random class FakeLocationStateUseCase( - private val fakeLocationModule: IFakeLocationModule, + private val fakeLocationModule: FakeLocationModule, private val permissionsModule: PermissionsPrivacyModule, private val localStateRepository: LocalStateRepository, private val citiesRepository: CityDataSource, @@ -61,23 +61,15 @@ class FakeLocationStateUseCase( private val locationManager: LocationManager get() = appContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager - private fun acquireLocationPermission() { - if (appContext.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - permissionsModule.toggleDangerousPermission( - appDesc, - android.Manifest.permission.ACCESS_FINE_LOCATION, - true - ) - } + private fun hasAcquireLocationPermission(): Boolean { + return (appContext.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) + || permissionsModule.toggleDangerousPermission(appDesc, android.Manifest.permission.ACCESS_FINE_LOCATION, true) } private fun applySettings(isQuickPrivacyEnabled: Boolean, fakeLocation: Pair?, isSpecificLocation: Boolean = false) { _configuredLocationMode.value = computeLocationMode(fakeLocation, isSpecificLocation) - if (isQuickPrivacyEnabled && fakeLocation != null) { - if (permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) != AppOpModes.ALLOWED) { - permissionsModule.setAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpModes.ALLOWED) - } + if (isQuickPrivacyEnabled && fakeLocation != null && hasAcquireMockLocationPermission()) { fakeLocationModule.startFakeLocation() fakeLocationModule.setFakeLocation(fakeLocation.first.toDouble(), fakeLocation.second.toDouble()) localStateRepository.locationMode.value = configuredLocationMode.value.first @@ -87,6 +79,11 @@ class FakeLocationStateUseCase( } } + private fun hasAcquireMockLocationPermission(): Boolean { + return (permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) == AppOpModes.ALLOWED) + || permissionsModule.setAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpModes.ALLOWED) + } + fun setSpecificLocation(latitude: Float, longitude: Float) { if (!localStateRepository.isQuickPrivacyEnabled) { localStateRepository.setShowQuickPrivacyDisabledMessage(true) @@ -161,8 +158,11 @@ class FakeLocationStateUseCase( } } - fun startListeningLocation() { - requestLocationUpdates(localListener) + fun startListeningLocation(): Boolean { + return if (hasAcquireLocationPermission()) { + requestLocationUpdates(localListener) + true + } else false } fun stopListeningLocation() { @@ -170,7 +170,6 @@ class FakeLocationStateUseCase( } fun requestLocationUpdates(listener: LocationListener) { - acquireLocationPermission() try { locationManager.requestLocationUpdates( LocationManager.NETWORK_PROVIDER, // TODO: tight this with fakelocation module. diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt index 2b858e9540a7fae1ccef41120c03199e173bb4ec..d98cb5db8cc2e2dfd29d7b86826fa72d6ce7e50a 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt @@ -17,6 +17,7 @@ package foundation.e.privacycentralapp.features.location +import android.Manifest import android.annotation.SuppressLint import android.content.Context import android.location.Location @@ -24,6 +25,7 @@ import android.os.Bundle import android.text.Editable import android.view.View import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.NonNull import androidx.core.view.isVisible import androidx.core.widget.addTextChangedListener @@ -81,6 +83,16 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) private var inputJob: Job? = null + private val locationPermissionRequest = registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> + if (permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) + || permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) + ) { + viewModel.submitAction(Action.StartListeningLocation) + } // TODO: else. + } + companion object { private const val DEBOUNCE_PERIOD = 1000L } @@ -147,6 +159,13 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) is FakeLocationViewModel.SingleEvent.LocationUpdatedEvent -> { updateLocation(event.location, event.mode) } + is FakeLocationViewModel.SingleEvent.RequestLocationPermission -> { + // TODO for standalone: rationale dialog + locationPermissionRequest.launch(arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + )) + } } } } @@ -326,13 +345,13 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) override fun onResume() { super.onResume() - viewModel.submitAction(Action.EnterScreen) + viewModel.submitAction(Action.StartListeningLocation) binding.mapView.onResume() } override fun onPause() { super.onPause() - viewModel.submitAction(Action.LeaveScreen) + viewModel.submitAction(Action.StopListeningLocation) binding.mapView.onPause() } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt index af20a7298335a195988011f2300bc513ab663adb..afba3d0f6429b57eaf8b6abadae9c0ee74eabebe 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt @@ -86,8 +86,8 @@ class FakeLocationViewModel( fun submitAction(action: Action) = viewModelScope.launch { when (action) { - is Action.EnterScreen -> fakeLocationStateUseCase.startListeningLocation() - is Action.LeaveScreen -> fakeLocationStateUseCase.stopListeningLocation() + is Action.StartListeningLocation -> actionStartListeningLocation() + is Action.StopListeningLocation -> fakeLocationStateUseCase.stopListeningLocation() is Action.SetSpecificLocationAction -> setSpecificLocation(action) is Action.UseRandomLocationAction -> fakeLocationStateUseCase.setRandomLocation() is Action.UseRealLocationAction -> @@ -97,18 +97,26 @@ class FakeLocationViewModel( } } + private suspend fun actionStartListeningLocation() { + val started = fakeLocationStateUseCase.startListeningLocation() + if (!started) { + _singleEvents.emit(SingleEvent.RequestLocationPermission) + } + } + private suspend fun setSpecificLocation(action: Action.SetSpecificLocationAction) { specificLocationInputFlow.emit(action) } sealed class SingleEvent { data class LocationUpdatedEvent(val mode: LocationMode, val location: Location?) : SingleEvent() + object RequestLocationPermission: SingleEvent() data class ErrorEvent(val error: String) : SingleEvent() } sealed class Action { - object EnterScreen : Action() - object LeaveScreen : Action() + object StartListeningLocation : Action() + object StopListeningLocation : Action() object UseRealLocationAction : Action() object UseRandomLocationAction : Action() data class SetSpecificLocationAction( diff --git a/app/src/standalone/res/values-night/colors.xml b/app/src/standalone/res/values-night/colors.xml new file mode 100644 index 0000000000000000000000000000000000000000..079b968ba33a68bb7a89808dc24c60abffaf17b3 --- /dev/null +++ b/app/src/standalone/res/values-night/colors.xml @@ -0,0 +1,28 @@ + + + + + #272727 + #5DB2FF + + #CCFFFFFF + #8CFFFFFF + + #121212 + + \ No newline at end of file diff --git a/app/src/standalone/res/values/colors.xml b/app/src/standalone/res/values/colors.xml new file mode 100644 index 0000000000000000000000000000000000000000..bd2792207d83028316a6690e71f7fc6bf6f41618 --- /dev/null +++ b/app/src/standalone/res/values/colors.xml @@ -0,0 +1,27 @@ + + + + + #FFFFFF + #0086FF + + #CC000000 + #8C000000 + + #FAFAFA + \ No newline at end of file diff --git a/app/src/standalone/res/values/strings.xml b/app/src/standalone/res/values/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..7719e7b9a59b85e0b4951162dd050e0e2421a480 --- /dev/null +++ b/app/src/standalone/res/values/strings.xml @@ -0,0 +1,3 @@ + + A-P - Standalone + diff --git a/build.gradle b/build.gradle index 2442f0188f2f2dac62086350bef63d721c83cc61..5222057fed9e3b54bf5776daa3e04ac42d853d4d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,11 +6,11 @@ buildscript { ext.buildConfig = [ 'compileSdk': 31, 'minSdk' : 26, - 'targetSdk' : 30, + 'targetSdk' : 31, 'version' : [ 'major': 1, - 'minor': 1, - 'patch': 2, + 'minor': 2, + 'patch': 0, ], ] @@ -31,7 +31,7 @@ buildscript { dependencies { classpath Libs.androidGradlePlugin - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" + classpath Libs.Kotlin.gradlePlugin // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -41,6 +41,7 @@ buildscript { plugins { id 'com.diffplug.spotless' version '5.12.4' id 'com.github.ben-manes.versions' version '0.38.0' + id 'org.jetbrains.kotlin.android' version '1.6.10' apply false } allprojects { @@ -49,7 +50,6 @@ allprojects { kotlinOptions { freeCompilerArgs = ['-Xjvm-default=enable', '-opt-in=kotlin.RequiresOptIn'] - jvmTarget = "1.8" } } diff --git a/dependencies.gradle b/dependencies.gradle index dcb9f9de6e29034972734aa71e7cfcd5becf548e..ed329c786658529c5c56b0e8dd4831edf451b6ac 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -6,7 +6,7 @@ def versions = [ ] ext.Versions = versions -libs.androidGradlePlugin = "com.android.tools.build:gradle:4.1.3" +libs.androidGradlePlugin = "com.android.tools.build:gradle:7.2.1" libs.timber = "com.jakewharton.timber:timber:4.7.1" @@ -25,7 +25,9 @@ libs.Kotlin = [ gradlePlugin: "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin", ] -versions.coroutines = "1.4.2" + + +versions.coroutines = "1.6.1" libs.Coroutines = [ core: "org.jetbrains.kotlinx:kotlinx-coroutines-core:$versions.coroutines", android: "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.coroutines", @@ -36,7 +38,10 @@ libs.AndroidX = [ collection: "androidx.collection:collection-ktx:1.1.0", palette: "androidx.palette:palette:1.0.0", archCoreTesting: "androidx.arch.core:core-testing:2.1.0", - coreKtx: "androidx.core:core-ktx:1.5.0-beta01", + coreKtx: "androidx.core:core-ktx:1.8.0", + coreKtxAPI29: "androidx.core:core-ktx:1.6.0", + appCompat: 'androidx.appcompat:appcompat:1.4.2', + work: 'androidx.work:work-runtime-ktx:2.7.1', ] versions.fragment = "1.5.0" @@ -82,7 +87,17 @@ libs.Hilt = [ gradlePlugin: "com.google.dagger:hilt-android-gradle-plugin:$versions.hilt", ] +libs.material = 'com.google.android.material:material:1.6.1' + +libs.Retrofit = [ + retrofit: 'com.squareup.retrofit2:retrofit:2.9.0', + scalars: 'com.squareup.retrofit2:converter-scalars:2.9.0' +] + + versions.mapbox="9.6.1" libs.MapBox = [ sdk: "com.mapbox.mapboxsdk:mapbox-android-sdk:$versions.mapbox" ] + +libs.mpAndroidCharts = 'com.github.PhilJay:MPAndroidChart:v3.1.0' diff --git a/fakelocation/.gitignore b/fakelocation/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..42afabfd2abebf31384ca7797186a27a4b7dbee8 --- /dev/null +++ b/fakelocation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/fakelocation/build.gradle b/fakelocation/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..ea28e4493de545156c50f07e2963846ce61ec0d2 --- /dev/null +++ b/fakelocation/build.gradle @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} + +android { + compileSdkVersion buildConfig.compileSdk + + defaultConfig { + minSdkVersion buildConfig.minSdk + targetSdkVersion buildConfig.targetSdk + + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + implementation ( + Libs.Kotlin.stdlib, + Libs.AndroidX.coreKtx, + Libs.Coroutines.core + ) +} diff --git a/fakelocation/consumer-rules.pro b/fakelocation/consumer-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/fakelocation/fakelocationdemo/.gitignore b/fakelocation/fakelocationdemo/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..42afabfd2abebf31384ca7797186a27a4b7dbee8 --- /dev/null +++ b/fakelocation/fakelocationdemo/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/fakelocation/fakelocationdemo/build.gradle b/fakelocation/fakelocationdemo/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..12ed2e797bb5156c00e7c384de7dbc771f7f08a4 --- /dev/null +++ b/fakelocation/fakelocationdemo/build.gradle @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + compileSdkVersion buildConfig.compileSdk + + defaultConfig { + applicationId "foundation.e.privacymodules.fakelocationdemo" + minSdkVersion buildConfig.minSdk + targetSdkVersion buildConfig.targetSdk + + versionCode buildConfig.version.code + versionName buildConfig.version.name + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + allWarningsAsErrors = false + } + buildFeatures { + dataBinding true + } +} + +dependencies { + implementation project(':api') + implementation project(':fakelocation') + implementation project(':permissionsstandalone') + + + implementation ( + Libs.Kotlin.stdlib, + Libs.AndroidX.coreKtx, + Libs.AndroidX.appCompat, + Libs.material, + ) + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/fakelocation/fakelocationdemo/proguard-rules.pro b/fakelocation/fakelocationdemo/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce --- /dev/null +++ b/fakelocation/fakelocationdemo/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/fakelocation/fakelocationdemo/src/main/AndroidManifest.xml b/fakelocation/fakelocationdemo/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..202599a328d91031dcb428cc20bf7d417a0d3cbd --- /dev/null +++ b/fakelocation/fakelocationdemo/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..1b0a35bef7ab10e3d2c178c56e4e20f4934526a7 --- /dev/null +++ b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacymodules.fakelocationdemo + +import android.Manifest +import android.app.AppOpsManager +import android.content.Context +import android.content.pm.PackageManager +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import android.os.Bundle +import android.os.Process.myUid +import android.util.Log +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat +import androidx.databinding.DataBindingUtil +import foundation.e.privacymodules.fakelocation.FakeLocationModule +import foundation.e.privacymodules.fakelocationdemo.databinding.ActivityMainBinding +import foundation.e.privacymodules.permissions.PermissionsPrivacyModule +import foundation.e.privacymodules.permissions.data.AppOpModes +import foundation.e.privacymodules.permissions.data.ApplicationDescription + +class MainActivity : AppCompatActivity() { + companion object { + const val TAG = "fakeLoc" + } + + private val fakeLocationModule: FakeLocationModule by lazy { FakeLocationModule(this) } + private val permissionsModule by lazy { PermissionsPrivacyModule(this) } + + private lateinit var binding: ActivityMainBinding + + private val appDesc by lazy { + ApplicationDescription( + packageName = packageName, + uid = myUid(), + label = getString(R.string.app_name), + icon = null + ) + } + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_main) + + actionBar?.setDisplayHomeAsUpEnabled(true) + + binding.view = this + } + + override fun onResume() { + super.onResume() + updateData("") + } + + private fun updateData(mockedLocation: String) { + binding.granted = permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) == AppOpModes.ALLOWED + + binding.mockedLocation = mockedLocation + } + + private val listener = object: LocationListener { + override fun onLocationChanged(location: Location) { + binding.currentLocation = "lat: ${location.latitude} - lon: ${location.longitude}" + } + + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + TODO("Not yet implemented") + } + + override fun onProviderEnabled(provider: String) { + binding.providerInfo = "onProdivderEnabled: $provider" + } + + override fun onProviderDisabled(provider: String) { + binding.providerInfo = "onProdivderDisabled: $provider" + } + } + + @Suppress("UNUSED_PARAMETER") + fun onClickToggleListenLocation(view: View?) { + val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager + + if (binding.toggleListenLocation.isChecked) { + requireLocationPermissions() + return + } + + locationManager.removeUpdates(listener) + binding.currentLocation = "no listening" + } + + private fun startLocationUpdates() { + val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager + + try { + Log.d(TAG, "requestLocationUpdates") + locationManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, // TODO: tight this with fakelocation module. + 1000L, + 1f, + listener + ) + binding.currentLocation = "listening started" + } catch (se: SecurityException) { + Log.e(TAG, "Missing permission", se) + } + } + + private val locationPermissionRequest = registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> + if (permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) + || permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) + ) { + startLocationUpdates() + } + } + + private fun requireLocationPermissions() { + if (ContextCompat.checkSelfPermission(this, + Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + startLocationUpdates() + } else { + // Before you perform the actual permission request, check whether your app + // already has the permissions, and whether your app needs to show a permission + // rationale dialog. For more details, see Request permissions. + locationPermissionRequest.launch( + arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + ) + } + + } + + @Suppress("UNUSED_PARAMETER") + fun onClickPermission(view: View?) { + val isGranted = permissionsModule.setAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, + AppOpModes.ALLOWED) + + if (isGranted) { + updateData("") + return + } + //dev mode disabled + val alertDialog = AlertDialog.Builder(this) + alertDialog + .setTitle("Mock location disabled") + .setNegativeButton("Cancel") { _, _ -> + } + alertDialog.create().show() + } + + @Suppress("UNUSED_PARAMETER") + fun onClickReset(view: View?) { + try { + fakeLocationModule.stopFakeLocation() + } catch(e: Exception) { + Log.e(TAG, "Can't stop FakeLocation", e) + } + } + + private fun setFakeLocation(latitude: Double, longitude: Double) { + try { + fakeLocationModule.startFakeLocation() + } catch(e: Exception) { + Log.e(TAG, "Can't startFakeLocation", e) + } + fakeLocationModule.setFakeLocation(latitude, longitude) + updateData("lat: ${latitude} - lon: ${longitude}") + } + + @Suppress("UNUSED_PARAMETER") + fun onClickParis(view: View?) { + setFakeLocation(48.8502282, 2.3542286) + } + + @Suppress("UNUSED_PARAMETER") + fun onClickLondon(view: View?) { + setFakeLocation(51.5287718, -0.2416803) + } + + @Suppress("UNUSED_PARAMETER") + fun onClickAmsterdam(view: View?) { + setFakeLocation(52.3547498, 4.8339211) + } +} diff --git a/fakelocation/fakelocationdemo/src/main/res/layout/activity_main.xml b/fakelocation/fakelocationdemo/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000000000000000000000000000000000..33fce699a4bc61e33b29f66650f19e19d290445c --- /dev/null +++ b/fakelocation/fakelocationdemo/src/main/res/layout/activity_main.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + +