diff options
author | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2022-09-07 12:30:29 +0000 |
---|---|---|
committer | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2022-09-07 12:30:29 +0000 |
commit | df7f3d969e0338acbb7efff6a3361f9aed927cf7 (patch) | |
tree | 37c1359cdf5573bfd90e23c138aa8aedfd341238 | |
parent | 54fcb13a713993687e8385dd2ff6d70563b5a1c2 (diff) |
2 - Convert trackers to kotlin, fix trackers count for system app
34 files changed, 1360 insertions, 1524 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index a44a00a..2e24d4c 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -43,7 +43,6 @@ import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersV import foundation.e.privacymodules.fakelocation.FakeLocationModule import foundation.e.privacymodules.ipscrambler.IpScramblerModule import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule -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 @@ -98,7 +97,7 @@ class DependencyContainer(val app: Application) { } val trackersStateUseCase by lazy { - TrackersStateUseCase(blockTrackersPrivacyModule, trackTrackersPrivacyModule, permissionsModule, localStateRepository, trackersRepository, appListsRepository, GlobalScope) + TrackersStateUseCase(blockTrackersPrivacyModule, trackTrackersPrivacyModule, localStateRepository, trackersRepository, appListsRepository, GlobalScope) } private val fakeLocationStateUseCase by lazy { diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt index 1f23516..d6af1e0 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt @@ -36,6 +36,10 @@ class AppListsRepository( private val context: Context, private val coroutineScope: CoroutineScope ) { + companion object { + private const val settingsPackageName = "com.android.settings" + } + val dummySystemApp = ApplicationDescription( packageName = "foundation.e.dummysystemapp", uid = -1, @@ -96,7 +100,9 @@ class AppListsRepository( } private fun isNotHiddenSystemApp(app: ApplicationInfo, launcherApps: List<String>): Boolean { - if (app.hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) { + if (app.packageName == settingsPackageName) { + return false + } else if (app.hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) { return true } else if (!app.hasFlag(ApplicationInfo.FLAG_SYSTEM)) { return true diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt index 2b2c1dd..b5310e1 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt @@ -20,7 +20,7 @@ package foundation.e.privacycentralapp.data.repositories import android.content.Context import android.util.Log import com.google.gson.Gson -import foundation.e.privacymodules.trackers.Tracker +import foundation.e.privacymodules.trackers.api.Tracker import retrofit2.Retrofit import retrofit2.converter.scalars.ScalarsConverterFactory import retrofit2.http.GET diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt index 6417fce..10c1ad0 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt @@ -22,17 +22,15 @@ import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.data.repositories.TrackersRepository import foundation.e.privacymodules.permissions.PermissionsPrivacyModule import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.IBlockTrackersPrivacyModule -import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule -import foundation.e.privacymodules.trackers.Tracker +import foundation.e.privacymodules.trackers.api.IBlockTrackersPrivacyModule +import foundation.e.privacymodules.trackers.api.ITrackTrackersPrivacyModule +import foundation.e.privacymodules.trackers.api.Tracker import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch class TrackersStateUseCase( private val blockTrackersPrivacyModule: IBlockTrackersPrivacyModule, private val trackersPrivacyModule: ITrackTrackersPrivacyModule, - private val permissionsPrivacyModule: PermissionsPrivacyModule, private val localStateRepository: LocalStateRepository, private val trackersRepository: TrackersRepository, private val appListsRepository: AppListsRepository, @@ -65,13 +63,8 @@ class TrackersStateUseCase( return appListsRepository.getApplicationDescription(appUid) } - fun isWhitelisted(appUid: Int): Boolean { - return if (appUid == appListsRepository.dummySystemApp.uid) { - appListsRepository.getHiddenSystemApps().any { - blockTrackersPrivacyModule.isWhitelisted(it.uid) - } - } else blockTrackersPrivacyModule.isWhitelisted(appUid) - } + fun isWhitelisted(appUid: Int) + = isWhitelisted(appUid, appListsRepository, blockTrackersPrivacyModule) fun getTrackersWhitelistIds(appUid: Int): List<String> { return if (appUid == appListsRepository.dummySystemApp.uid) { @@ -114,3 +107,16 @@ class TrackersStateUseCase( trackersPrivacyModule.start(trackersRepository.trackers, enableNotification = false) } } + + +fun isWhitelisted( + appUid: Int, + appListsRepository: AppListsRepository, + blockTrackersPrivacyModule: IBlockTrackersPrivacyModule +): Boolean { + return if (appUid == appListsRepository.dummySystemApp.uid) { + appListsRepository.getHiddenSystemApps().any { + blockTrackersPrivacyModule.isWhitelisted(it.uid) + } + } else blockTrackersPrivacyModule.isWhitelisted(appUid) +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt index 5abe0b8..52e0bad 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt @@ -18,15 +18,16 @@ package foundation.e.privacycentralapp.domain.usecases import android.content.res.Resources +import android.util.Log import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.common.throttleFirst import foundation.e.privacycentralapp.data.repositories.AppListsRepository import foundation.e.privacycentralapp.domain.entities.AppWithCounts import foundation.e.privacycentralapp.domain.entities.TrackersPeriodicStatistics import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.IBlockTrackersPrivacyModule -import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule -import foundation.e.privacymodules.trackers.Tracker +import foundation.e.privacymodules.trackers.api.IBlockTrackersPrivacyModule +import foundation.e.privacymodules.trackers.api.ITrackTrackersPrivacyModule +import foundation.e.privacymodules.trackers.api.Tracker import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -172,24 +173,45 @@ class TrackersStatisticsUseCase( .map { apps -> val callsByApp = trackTrackersPrivacyModule.getPastDayTrackersCallsByApps() apps.map { app -> - val (blockedLeaks, leaks) = callsByApp.getOrDefault(app.uid, 0 to 0) AppWithCounts( app = app, isWhitelisted = !blockTrackersPrivacyModule.isBlockingEnabled() || - blockTrackersPrivacyModule.isWhitelisted(app.uid), - trackersCount = appListsRepository.foldForHiddenSystemApp(app.uid) { - trackersCounts.getOrDefault(it, 0) + isWhitelisted(app.uid, appListsRepository, blockTrackersPrivacyModule), + trackersCount = if (app.uid == appListsRepository.dummySystemApp.uid) { + getHiddenSystemAppsTrackersCount() + } else { + trackersCounts.getOrDefault(app.uid, 0) }, - whiteListedTrackersCount = appListsRepository.foldForHiddenSystemApp(app.uid) { - blockTrackersPrivacyModule.getWhiteList(it).size + whiteListedTrackersCount = if (app.uid == appListsRepository.dummySystemApp.uid) { + getHiddenSystemAppWhitelistedTrackersCount() + } else { + blockTrackersPrivacyModule.getWhiteList(app.uid).size }, - blockedLeaks = blockedLeaks, - leaks = leaks + blockedLeaks = appListsRepository.foldForHiddenSystemApp(app.uid) { + appUid -> callsByApp.getOrDefault(appUid, 0 to 0).first + }, + leaks = appListsRepository.foldForHiddenSystemApp(app.uid) { + appUid -> callsByApp.getOrDefault(appUid, 0 to 0).second + } ) }.sortedWith(mostLeakedAppsComparator) } } + private fun getHiddenSystemAppsTrackersCount(): Int { + return trackTrackersPrivacyModule.getTrackersCount( + appListsRepository.getHiddenSystemApps().map { it.uid } + ) + } + + private fun getHiddenSystemAppWhitelistedTrackersCount(): Int { + return appListsRepository.getHiddenSystemApps().fold(HashSet<String>()) { acc, app -> + acc.addAll(blockTrackersPrivacyModule.getWhiteList(app.uid).map { it.id }) + acc + }.size + } + + private val mostLeakedAppsComparator: Comparator<AppWithCounts> = Comparator { o1, o2 -> val leaks = o2.leaks - o1.leaks if (leaks != 0) leaks else { diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt index dab0b18..f70065c 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt @@ -18,7 +18,7 @@ package foundation.e.privacycentralapp.domain.usecases import foundation.e.privacycentralapp.data.repositories.LocalStateRepository -import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule +import foundation.e.privacymodules.trackers.api.ITrackTrackersPrivacyModule class UpdateWidgetUseCase( private val localStateRepository: LocalStateRepository, diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt index 9a294e2..230f872 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt @@ -18,7 +18,7 @@ package foundation.e.privacycentralapp.features.trackers.apptrackers import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.Tracker +import foundation.e.privacymodules.trackers.api.Tracker data class AppTrackersState( val appDesc: ApplicationDescription? = null, diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt index bc591bf..c20ec7c 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt @@ -23,7 +23,7 @@ import androidx.lifecycle.viewModelScope import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase -import foundation.e.privacymodules.trackers.Tracker +import foundation.e.privacymodules.trackers.api.Tracker import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt index 02a274a..197f13f 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt @@ -27,7 +27,7 @@ import android.widget.TextView import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import foundation.e.privacycentralapp.R -import foundation.e.privacymodules.trackers.Tracker +import foundation.e.privacymodules.trackers.api.Tracker class ToggleTrackersAdapter( private val itemsLayout: Int, diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/location/IFakeLocationModule.kt b/fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/IFakeLocationModule.kt index ecad2a4..32906f8 100644 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/location/IFakeLocationModule.kt +++ b/fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/IFakeLocationModule.kt @@ -15,7 +15,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package foundation.e.privacymodules.location +package foundation.e.privacymodules.fakelocation /** * Manage a fake location on the device. diff --git a/trackers/build.gradle b/trackers/build.gradle index dec05ff..409996a 100644 --- a/trackers/build.gradle +++ b/trackers/build.gradle @@ -43,4 +43,9 @@ android { dependencies {
implementation project(':privacymodule-api')
+ implementation(
+ Libs.Kotlin.stdlib,
+ Libs.AndroidX.coreKtx,
+ Libs.Coroutines.core
+ )
}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.java deleted file mode 100644 index 80f00c1..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.java +++ /dev/null @@ -1,173 +0,0 @@ -/*
- 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/DNSBlockerRunnable.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt new file mode 100644 index 0000000..01ae5b7 --- /dev/null +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt @@ -0,0 +1,164 @@ +/*
+ 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.system.ErrnoException
+import android.system.Os
+import android.system.OsConstants
+import android.util.Log
+import foundation.e.privacymodules.trackers.data.TrackersRepository
+import foundation.e.privacymodules.trackers.data.WhitelistRepository
+import java.io.BufferedReader
+import java.io.IOException
+import java.io.InputStreamReader
+import java.io.PrintWriter
+
+class DNSBlockerRunnable(
+ ct: Context,
+ private val trackersLogger: TrackersLogger,
+ private val trackersRepository: TrackersRepository,
+ private val whitelistRepository: WhitelistRepository
+) : Runnable {
+ var resolverReceiver: LocalServerSocket? = null
+ var stopped = false
+ private var eBrowserAppUid = -1
+
+ companion object {
+ private const val SOCKET_NAME = "foundation.e.advancedprivacy"
+ private const val E_BROWSER_DOT_SERVER = "chrome.cloudflare-dns.com"
+ private const val TAG = "DNSBlockerRunnable"
+ }
+
+ init {
+ initEBrowserDoTFix(ct)
+ }
+
+ @Synchronized
+ fun stop() {
+ stopped = true
+ closeSocket()
+ }
+
+ private fun 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!!.fileDescriptor, OsConstants.SHUT_RDWR)
+ resolverReceiver!!.close()
+ resolverReceiver = null
+ } catch (e: ErrnoException) {
+ 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 (e: Exception) {
+ Log.e(TAG, "Exception: cannot close DNS port on stop $SOCKET_NAME !", e)
+ }
+ }
+ }
+
+ override fun run() {
+ val resolverReceiver = try {
+ LocalServerSocket(SOCKET_NAME)
+ } catch (eio: IOException) {
+ Log.e(TAG, "Exception:Cannot open DNS port $SOCKET_NAME !", eio)
+ return
+ }
+
+ this.resolverReceiver = resolverReceiver
+ Log.d(TAG, "DNSFilterProxy running on port $SOCKET_NAME !")
+
+ while (!stopped) {
+ try {
+ val socket = resolverReceiver.accept()
+ val reader = BufferedReader(InputStreamReader(socket.inputStream))
+ val line = reader.readLine()
+ val params = line.split(",").toTypedArray()
+ val output = socket.outputStream
+ val writer = PrintWriter(output, true)
+ val domainName = params[0]
+ val appUid = params[1].toInt()
+ var isBlocked = false
+ if (isEBrowserDoTBlockFix(appUid, domainName)) {
+ isBlocked = true
+ } else if (trackersRepository.isTracker(domainName)) {
+ val trackerId = trackersRepository.getTrackerId(domainName)
+ if (shouldBlock(appUid, trackerId)) {
+ writer.println("block")
+ isBlocked = true
+ }
+ trackersLogger.logAccess(trackerId, appUid, isBlocked)
+ }
+ if (!isBlocked) {
+ writer.println("pass")
+ }
+ socket.close()
+ // Printing bufferedreader data
+ } catch (e: IOException) {
+ Log.w(TAG, "Exception while listening DNS resolver", e)
+ }
+ }
+ }
+
+ private fun initEBrowserDoTFix(context: Context) {
+ try {
+ eBrowserAppUid =
+ context.packageManager.getApplicationInfo("foundation.e.browser", 0).uid
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.i(TAG, "no E Browser package found.")
+ }
+ }
+
+ private fun isEBrowserDoTBlockFix(appUid: Int, hostname: String): Boolean {
+ return appUid == eBrowserAppUid && E_BROWSER_DOT_SERVER == hostname
+ }
+
+ private fun shouldBlock(appUid: Int, trackerId: String?): Boolean {
+ return whitelistRepository.isBlockingEnabled &&
+ !whitelistRepository.isAppWhiteListed(appUid) &&
+ !whitelistRepository.isTrackerWhiteListedForApp(trackerId, appUid)
+ }
+
+
+}
\ No newline at end of file diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.java deleted file mode 100644 index 6250621..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - 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/DNSBlockerService.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.kt new file mode 100644 index 0000000..3162422 --- /dev/null +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.kt @@ -0,0 +1,80 @@ +/* + 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 + +class DNSBlockerService : Service() { + private var trackersLogger: TrackersLogger? = null + + companion object { + private const val TAG = "DNSBlockerService" + private var sDNSBlocker: DNSBlockerRunnable? = null + const val ACTION_START = "foundation.e.privacymodules.trackers.intent.action.START" + const val EXTRA_ENABLE_NOTIFICATION = + "foundation.e.privacymodules.trackers.intent.extra.ENABLED_NOTIFICATION" + } + + override fun onBind(intent: Intent): IBinder? { + // TODO: Return the communication channel to the service. + throw UnsupportedOperationException("Not yet implemented") + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + if (intent.getBooleanExtra(EXTRA_ENABLE_NOTIFICATION, true)) { + ForegroundStarter.startForeground(this) + } + if (ACTION_START == intent.action) { + stop() + start() + } + return START_STICKY + } + + private fun start() { + try { + val trackersLogger = TrackersLogger(this) + this.trackersLogger = trackersLogger + + sDNSBlocker = DNSBlockerRunnable( + this, + trackersLogger, + TrackersRepository.getInstance(), + WhitelistRepository.getInstance(this) + ) + Thread(sDNSBlocker).start() + } catch (e: Exception) { + Log.e(TAG, "Error while starting DNSBlocker service", e) + stop() + } + } + + private fun stop() { + sDNSBlocker?.stop() + sDNSBlocker = 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 deleted file mode 100644 index 1563163..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - 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/ForegroundStarter.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.kt new file mode 100644 index 0000000..30bba7b --- /dev/null +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.kt @@ -0,0 +1,46 @@ +/* + 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.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Context +import android.os.Build + +object ForegroundStarter { + private const val NOTIFICATION_CHANNEL_ID = "blocker_service" + fun startForeground(service: Service) { + val mNotificationManager = + service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + if (Build.VERSION.SDK_INT >= 26) { + mNotificationManager.createNotificationChannel( + NotificationChannel( + NOTIFICATION_CHANNEL_ID, + NOTIFICATION_CHANNEL_ID, + NotificationManager.IMPORTANCE_LOW + ) + ) + val notification = Notification.Builder(service, NOTIFICATION_CHANNEL_ID) + .setContentTitle("Trackers filter").build() + service.startForeground(1337, notification) + } + } +}
\ No newline at end of file diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.java deleted file mode 100644 index 3710253..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - 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/TrackersLogger.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt new file mode 100644 index 0000000..6d2abec --- /dev/null +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt @@ -0,0 +1,69 @@ +/* + 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 foundation.e.privacymodules.trackers.data.StatsRepository +import java.util.concurrent.LinkedBlockingQueue + +class TrackersLogger(context: Context) { + private val statsRepository = StatsRepository.getInstance(context) + private val queue = LinkedBlockingQueue<DetectedTracker>() + private var stopped = false + + companion object { + private const val TAG = "TrackerModule" + } + + init { + startWriteLogLoop() + } + + fun stop() { + stopped = true + } + + fun logAccess(trackerId: String?, appId: Int, wasBlocked: Boolean) { + queue.offer(DetectedTracker(trackerId, appId, wasBlocked)) + } + + private fun startWriteLogLoop() { + val writeLogRunner = Runnable { + while (!stopped) { + try { + logAccess(queue.take()) + } catch (e: InterruptedException) { + Log.e(TAG, "writeLogLoop detectedTrackersQueue.take() interrupted: ", e) + } + } + } + Thread(writeLogRunner).start() + } + + fun logAccess(detectedTracker: DetectedTracker) { + statsRepository.logAccess( + detectedTracker.trackerId, + detectedTracker.appUid, + detectedTracker.wasBlocked + ) + } + + inner class DetectedTracker(var trackerId: String?, var appUid: Int, var wasBlocked: Boolean) +}
\ No newline at end of file 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 deleted file mode 100644 index ea62766..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - 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/BlockTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt new file mode 100644 index 0000000..46729fd --- /dev/null +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt @@ -0,0 +1,91 @@ +/* + 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.api + +import foundation.e.privacymodules.trackers.data.WhitelistRepository +import android.content.Context +import foundation.e.privacymodules.trackers.data.TrackersRepository + +class BlockTrackersPrivacyModule(context: Context) : IBlockTrackersPrivacyModule { + private val mListeners = mutableListOf<IBlockTrackersPrivacyModule.Listener>() + private val trackersRepository = TrackersRepository.getInstance() + private val whitelistRepository = WhitelistRepository.getInstance(context) + + companion object { + private var instance: BlockTrackersPrivacyModule? = null + + fun getInstance(context: Context): BlockTrackersPrivacyModule { + return instance?: BlockTrackersPrivacyModule(context).apply { instance = this } + } + } + + override fun addListener(listener: IBlockTrackersPrivacyModule.Listener) { + mListeners.add(listener) + } + + override fun clearListeners() { + mListeners.clear() + } + + override fun disableBlocking() { + whitelistRepository.isBlockingEnabled = false + mListeners.forEach { listener -> listener.onBlockingToggle(false) } + } + + override fun enableBlocking() { + whitelistRepository.isBlockingEnabled = true + mListeners.forEach { listener -> listener.onBlockingToggle(true) } + } + + override fun getWhiteList(appUid: Int): List<Tracker> { + return whitelistRepository.getWhiteListForApp(appUid).mapNotNull { + trackersRepository.getTracker(it) + } + } + + override fun getWhiteListedApp(): List<Int> { + return whitelistRepository.whiteListedApp + } + + override fun isBlockingEnabled(): Boolean { + return whitelistRepository.isBlockingEnabled + } + + override fun isWhiteListEmpty(): Boolean { + return whitelistRepository.areWhiteListEmpty() + } + + override fun isWhitelisted(appUid: Int): Boolean { + return whitelistRepository.isAppWhiteListed(appUid) + } + + override fun removeListener(listener: IBlockTrackersPrivacyModule.Listener) { + mListeners.remove(listener) + } + + override fun setWhiteListed(tracker: Tracker, appUid: Int, isWhiteListed: Boolean) { + whitelistRepository.setWhiteListed(tracker, appUid, isWhiteListed) + } + + override fun setWhiteListed(appUid: Int, isWhiteListed: Boolean) { + whitelistRepository.setWhiteListed(appUid, isWhiteListed) + } + + +}
\ No newline at end of file diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/trackers/IBlockTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt index 53b540e..b07e210 100644 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/trackers/IBlockTrackersPrivacyModule.kt +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt @@ -15,8 +15,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package foundation.e.privacymodules.trackers - +package foundation.e.privacymodules.trackers.api /** * Manage trackers blocking and whitelisting. diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/trackers/ITrackTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt index 139290e..f7beebd 100644 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/trackers/ITrackTrackersPrivacyModule.kt +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt @@ -15,7 +15,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package foundation.e.privacymodules.trackers +package foundation.e.privacymodules.trackers.api /** * Get reporting about trackers calls. @@ -30,9 +30,10 @@ interface ITrackTrackersPrivacyModule { fun getTrackersForApp(appUid: Int): List<Tracker> /** - * Return the number of encountered trackers since "ever" + * Return the number of encountered trackers since "ever", for the given [appUids], + * or all apps if [appUids] is null */ - fun getTrackersCount(): Int + fun getTrackersCount(appUids: List<Int>? = null): Int /** * Return the number of encountere trackers since "ever", for each app uid. @@ -75,7 +76,7 @@ interface ITrackTrackersPrivacyModule { fun getPastDayTrackersCallsByApps(): Map<Int, Pair<Int, Int>> - fun getPastDayTrackersCallsForApp(appUId: Int): Pair<Int, Int> + fun getPastDayTrackersCallsForApp(appUid: Int): Pair<Int, Int> fun getPastDayMostLeakedApp(): Int 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 deleted file mode 100644 index 38b2c8f..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - 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/api/TrackTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt new file mode 100644 index 0000000..e0672cc --- /dev/null +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt @@ -0,0 +1,113 @@ +/* + 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 foundation.e.privacymodules.trackers.DNSBlockerService +import foundation.e.privacymodules.trackers.data.StatsRepository +import foundation.e.privacymodules.trackers.data.TrackersRepository +import java.time.temporal.ChronoUnit + +class TrackTrackersPrivacyModule(private val context: Context) : ITrackTrackersPrivacyModule { + private val statsRepository = StatsRepository.getInstance(context) + private val listeners: MutableList<ITrackTrackersPrivacyModule.Listener> = mutableListOf() + + companion object { + private var instance: TrackTrackersPrivacyModule? = null + + fun getInstance(context: Context): TrackTrackersPrivacyModule { + return instance?: TrackTrackersPrivacyModule(context).apply { instance = this } + } + } + + init { + statsRepository.setNewDataCallback { + listeners.forEach { listener -> listener.onNewData() } + } + } + + override fun start(trackers: List<Tracker>, enableNotification: Boolean) { + TrackersRepository.getInstance().setTrackersList(trackers) + val intent = Intent(context, DNSBlockerService::class.java) + intent.action = DNSBlockerService.ACTION_START + intent.putExtra(DNSBlockerService.EXTRA_ENABLE_NOTIFICATION, enableNotification) + context.startService(intent) + } + + override fun getPastDayTrackersCalls(): List<Pair<Int, Int>> { + return statsRepository.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS) + } + + override fun getPastMonthTrackersCalls(): List<Pair<Int, Int>> { + return statsRepository.getTrackersCallsOnPeriod(30, ChronoUnit.DAYS) + } + + override fun getPastYearTrackersCalls(): List<Pair<Int, Int>> { + return statsRepository.getTrackersCallsOnPeriod(12, ChronoUnit.MONTHS) + } + + override fun getTrackersCount(appUids: List<Int>?): Int { + return statsRepository.getContactedTrackersCount(appUids) + } + + override fun getTrackersCountByApp(): Map<Int, Int> { + return statsRepository.getContactedTrackersCountByApp() + } + + override fun getTrackersForApp(appUid: Int): List<Tracker> { + return statsRepository.getAllTrackersOfApp(appUid) + } + + override fun getPastDayTrackersCount(): Int { + return statsRepository.getActiveTrackersByPeriod(24, ChronoUnit.HOURS) + } + + override fun getPastMonthTrackersCount(): Int { + return statsRepository.getActiveTrackersByPeriod(30, ChronoUnit.DAYS) + } + + override fun getPastYearTrackersCount(): Int { + return statsRepository.getActiveTrackersByPeriod(12, ChronoUnit.MONTHS) + } + + override fun getPastDayMostLeakedApp(): Int { + return statsRepository.getMostLeakedApp(24, ChronoUnit.HOURS) + } + + override fun getPastDayTrackersCallsByApps(): Map<Int, Pair<Int, Int>> { + return statsRepository.getCallsByApps(24, ChronoUnit.HOURS) + } + + override fun getPastDayTrackersCallsForApp(appUid: Int): Pair<Int, Int> { + return statsRepository.getCalls(appUid, 24, ChronoUnit.HOURS) + } + + override fun addListener(listener: ITrackTrackersPrivacyModule.Listener) { + listeners.add(listener) + } + + override fun removeListener(listener: ITrackTrackersPrivacyModule.Listener) { + listeners.remove(listener) + } + + override fun clearListeners() { + listeners.clear() + } +} diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/trackers/Tracker.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/Tracker.kt index 0a4395a..2da5b16 100644 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/trackers/Tracker.kt +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/Tracker.kt @@ -15,7 +15,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package foundation.e.privacymodules.trackers +package foundation.e.privacymodules.trackers.api /** * Describe a tracker. 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 deleted file mode 100644 index 0650114..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.java +++ /dev/null @@ -1,507 +0,0 @@ -/* - 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/StatsDatabase.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt new file mode 100644 index 0000000..86208ad --- /dev/null +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt @@ -0,0 +1,451 @@ +/* + 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.ContentValues +import android.content.Context +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import android.provider.BaseColumns +import foundation.e.privacymodules.trackers.api.Tracker +import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_APP_UID +import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED +import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED +import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TIMESTAMP +import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TRACKER +import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.TABLE_NAME +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit +import java.time.temporal.TemporalUnit +import java.util.concurrent.TimeUnit + +class StatsDatabase(context: Context) : + SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { + + companion object { + const val DATABASE_VERSION = 1 + const val DATABASE_NAME = "TrackerFilterStats.db" + private const val SQL_CREATE_TABLE = "CREATE TABLE $TABLE_NAME (" + + "${BaseColumns._ID} INTEGER PRIMARY KEY," + + "$COLUMN_NAME_TIMESTAMP INTEGER," + + "$COLUMN_NAME_APP_UID INTEGER," + + "$COLUMN_NAME_TRACKER TEXT," + + "$COLUMN_NAME_NUMBER_CONTACTED INTEGER," + + "$COLUMN_NAME_NUMBER_BLOCKED INTEGER)" + + private const val PROJECTION_NAME_PERIOD = "period" + private const val PROJECTION_NAME_CONTACTED_SUM = "contactedsum" + private const val PROJECTION_NAME_BLOCKED_SUM = "blockedsum" + private const val PROJECTION_NAME_LEAKED_SUM = "leakedsum" + private const val PROJECTION_NAME_TRACKERS_COUNT = "trackerscount" + } + + object AppTrackerEntry : BaseColumns { + const val TABLE_NAME = "tracker_filter_stats" + const val COLUMN_NAME_TIMESTAMP = "timestamp" + const val COLUMN_NAME_TRACKER = "tracker" + const val COLUMN_NAME_APP_UID = "app_uid" + const val COLUMN_NAME_NUMBER_CONTACTED = "sum_contacted" + const val COLUMN_NAME_NUMBER_BLOCKED = "sum_blocked" + } + + private var projection = arrayOf( + COLUMN_NAME_TIMESTAMP, + COLUMN_NAME_APP_UID, + COLUMN_NAME_TRACKER, + COLUMN_NAME_NUMBER_CONTACTED, + COLUMN_NAME_NUMBER_BLOCKED + ) + + private val lock = Any() + private val trackersRepository = TrackersRepository.getInstance() + + override fun onCreate(db: SQLiteDatabase) { + db.execSQL(SQL_CREATE_TABLE) + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + onCreate(db) + } + + private fun getCallsByPeriod( + periodsCount: Int, + periodUnit: TemporalUnit, + sqlitePeriodFormat: String + ): Map<String, Pair<Int, Int>> { + synchronized(lock) { + val minTimestamp = getPeriodStartTs(periodsCount, periodUnit) + val db = readableDatabase + val selection = "$COLUMN_NAME_TIMESTAMP >= ?" + val selectionArg = arrayOf("" + minTimestamp) + + val projection = ("$COLUMN_NAME_TIMESTAMP, " + + "STRFTIME('${sqlitePeriodFormat}', DATETIME($COLUMN_NAME_TIMESTAMP, 'unixepoch', 'localtime')) $PROJECTION_NAME_PERIOD," + + "SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM, " + + "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM") + + val cursor = db.rawQuery( + "SELECT $projection FROM $TABLE_NAME WHERE $selection" + + " GROUP BY $PROJECTION_NAME_PERIOD" + + " ORDER BY $COLUMN_NAME_TIMESTAMP DESC LIMIT $periodsCount", + selectionArg + ) + val callsByPeriod = HashMap<String, Pair<Int, Int>>() + while (cursor.moveToNext()) { + val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM) + val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM) + callsByPeriod[cursor.getString(PROJECTION_NAME_PERIOD)] = blocked to contacted - blocked + } + cursor.close() + db.close() + return callsByPeriod + } + } + + private fun callsByPeriodToPeriodsList( + callsByPeriod: Map<String, Pair<Int, Int>>, + periodsCount: Int, + periodUnit: TemporalUnit, + javaPeriodFormat: String + ): List<Pair<Int, Int>> { + var currentDate = ZonedDateTime.now().minus(periodsCount.toLong(), periodUnit) + val formater = DateTimeFormatter.ofPattern(javaPeriodFormat) + val calls = mutableListOf<Pair<Int, Int>>() + for (i in 0 until periodsCount) { + currentDate = currentDate.plus(1, periodUnit) + val currentPeriod = formater.format(currentDate) + calls.add(callsByPeriod.getOrDefault(currentPeriod, 0 to 0)) + } + return calls + } + + fun getTrackersCallsOnPeriod( + periodsCount: Int, + periodUnit: TemporalUnit + ): List<Pair<Int, Int>> { + var sqlitePeriodFormat = "%Y%m" + var 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" + } + val callsByPeriod = getCallsByPeriod(periodsCount, periodUnit, sqlitePeriodFormat) + return callsByPeriodToPeriodsList(callsByPeriod, periodsCount, periodUnit, javaPeriodFormat) + } + + fun getActiveTrackersByPeriod(periodsCount: Int, periodUnit: TemporalUnit): Int { + synchronized(lock) { + val minTimestamp = getPeriodStartTs(periodsCount, periodUnit) + val db = writableDatabase + val selection = "$COLUMN_NAME_TIMESTAMP >= ? AND " + + "$COLUMN_NAME_NUMBER_CONTACTED > $COLUMN_NAME_NUMBER_BLOCKED" + val selectionArg = arrayOf("" + minTimestamp) + val projection = + "COUNT(DISTINCT $COLUMN_NAME_TRACKER) $PROJECTION_NAME_TRACKERS_COUNT" + + val cursor = db.rawQuery( + "SELECT $projection FROM $TABLE_NAME WHERE $selection", + selectionArg + ) + var count = 0 + if (cursor.moveToNext()) { + count = cursor.getInt(0) + } + cursor.close() + db.close() + return count + } + } + + fun getContactedTrackersCount(appUids: List<Int>?): Int { + synchronized(lock) { + val db = readableDatabase + val projection = + "COUNT(DISTINCT $COLUMN_NAME_TRACKER) $PROJECTION_NAME_TRACKERS_COUNT" + + var query = "SELECT $projection FROM $TABLE_NAME" + + appUids?.let { + query += " WHERE $COLUMN_NAME_APP_UID IN (${it.joinToString(", ")})" + } + + val cursor = db.rawQuery(query, arrayOf()) + var count = 0 + if (cursor.moveToNext()) { + count = cursor.getInt(0) + } + cursor.close() + db.close() + return count + } + } + + fun getContactedTrackersCountByApp(): Map<Int, Int> { + synchronized(lock) { + val db = readableDatabase + val projection = "$COLUMN_NAME_APP_UID, " + + "COUNT(DISTINCT $COLUMN_NAME_TRACKER) $PROJECTION_NAME_TRACKERS_COUNT" + val cursor = db.rawQuery( + "SELECT $projection FROM $TABLE_NAME" + + " GROUP BY $COLUMN_NAME_APP_UID", + arrayOf() + ) + val countByApp = mutableMapOf<Int, Int>() + while (cursor.moveToNext()) { + countByApp[cursor.getInt(COLUMN_NAME_APP_UID)] = + cursor.getInt(PROJECTION_NAME_TRACKERS_COUNT) + } + cursor.close() + db.close() + return countByApp + } + } + + fun getCallsByApps(periodCount: Int, periodUnit: TemporalUnit): Map<Int, Pair<Int, Int>> { + synchronized(lock) { + val minTimestamp = getPeriodStartTs(periodCount, periodUnit) + val db = readableDatabase + val selection = "$COLUMN_NAME_TIMESTAMP >= ?" + val selectionArg = arrayOf("" + minTimestamp) + val projection = "$COLUMN_NAME_APP_UID, " + + "SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM," + + "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM" + val cursor = db.rawQuery( + "SELECT $projection FROM $TABLE_NAME" + + " WHERE $selection" + + " GROUP BY $COLUMN_NAME_APP_UID", + selectionArg + ) + val callsByApp = HashMap<Int, Pair<Int, Int>>() + while (cursor.moveToNext()) { + val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM) + val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM) + callsByApp[cursor.getInt(COLUMN_NAME_APP_UID)] = blocked to contacted - blocked + } + cursor.close() + db.close() + return callsByApp + } + } + + fun getCalls(appUid: Int, periodCount: Int, periodUnit: TemporalUnit): Pair<Int, Int> { + synchronized(lock) { + val minTimestamp = getPeriodStartTs(periodCount, periodUnit) + val db = readableDatabase + val selection = "$COLUMN_NAME_APP_UID = ? AND " + + "$COLUMN_NAME_TIMESTAMP >= ?" + val selectionArg = arrayOf("" + appUid, "" + minTimestamp) + val projection = + "SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM," + + "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM" + val cursor = db.rawQuery( + "SELECT $projection FROM $TABLE_NAME WHERE $selection", + selectionArg + ) + var calls: Pair<Int, Int> = 0 to 0 + if (cursor.moveToNext()) { + val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM) + val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM) + calls = blocked to contacted - blocked + } + cursor.close() + db.close() + return calls + } + } + + fun getMostLeakedApp(periodCount: Int, periodUnit: TemporalUnit): Int { + synchronized(lock) { + val minTimestamp = getPeriodStartTs(periodCount, periodUnit) + val db = readableDatabase + val selection = "$COLUMN_NAME_TIMESTAMP >= ?" + val selectionArg = arrayOf("" + minTimestamp) + val projection = "$COLUMN_NAME_APP_UID, " + + "SUM($COLUMN_NAME_NUMBER_CONTACTED - $COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_LEAKED_SUM" + val cursor = db.rawQuery( + "SELECT $projection FROM $TABLE_NAME" + + " WHERE $selection" + + " GROUP BY $COLUMN_NAME_APP_UID" + + " ORDER BY $PROJECTION_NAME_LEAKED_SUM DESC LIMIT 1", + selectionArg + ) + var appUid = 0 + if (cursor.moveToNext()) { + appUid = cursor.getInt(COLUMN_NAME_APP_UID) + } + cursor.close() + db.close() + return appUid + } + } + + fun logAccess(trackerId: String?, appUid: Int, blocked: Boolean) { + synchronized(lock) { + val currentHour = getCurrentHourTs() + val db = writableDatabase + val values = ContentValues() + values.put(COLUMN_NAME_APP_UID, appUid) + values.put(COLUMN_NAME_TRACKER, trackerId) + values.put(COLUMN_NAME_TIMESTAMP, currentHour) + + /*String query = "UPDATE product SET "+COLUMN_NAME_NUMBER_CONTACTED+" = "+COLUMN_NAME_NUMBER_CONTACTED+" + 1 "; + if(blocked) + query+=COLUMN_NAME_NUMBER_BLOCKED+" = "+COLUMN_NAME_NUMBER_BLOCKED+" + 1 "; +*/ + val selection = "$COLUMN_NAME_TIMESTAMP = ? AND " + + "$COLUMN_NAME_APP_UID = ? AND " + + "$COLUMN_NAME_TRACKER = ? " + val selectionArg = arrayOf("" + currentHour, "" + appUid, trackerId) + val cursor = db.query( + TABLE_NAME, + projection, + selection, + selectionArg, + null, + null, + null + ) + if (cursor.count > 0) { + cursor.moveToFirst() + val entry = cursorToEntry(cursor) + if (blocked) values.put( + COLUMN_NAME_NUMBER_BLOCKED, + entry.sum_blocked + 1 + ) else values.put(COLUMN_NAME_NUMBER_BLOCKED, entry.sum_blocked) + values.put(COLUMN_NAME_NUMBER_CONTACTED, entry.sum_contacted + 1) + db.update(TABLE_NAME, values, selection, selectionArg) + + // db.execSQL(query, new String[]{""+hour, ""+day, ""+month, ""+year, ""+appUid, ""+trackerId}); + } else { + if (blocked) values.put( + COLUMN_NAME_NUMBER_BLOCKED, + 1 + ) else values.put(COLUMN_NAME_NUMBER_BLOCKED, 0) + values.put(COLUMN_NAME_NUMBER_CONTACTED, 1) + db.insert(TABLE_NAME, null, values) + } + cursor.close() + db.close() + } + } + + private fun cursorToEntry(cursor: Cursor): StatEntry { + val entry = StatEntry() + entry.timestamp = + cursor.getLong(COLUMN_NAME_TIMESTAMP) + entry.app_uid = cursor.getInt(COLUMN_NAME_APP_UID) + entry.sum_blocked = cursor.getInt(COLUMN_NAME_NUMBER_BLOCKED) + entry.sum_contacted = cursor.getInt(COLUMN_NAME_NUMBER_CONTACTED) + entry.tracker = cursor.getInt(COLUMN_NAME_TRACKER) + return entry + } + + fun getAllTrackersOfApp(appUid: Int): List<Tracker> { + synchronized(lock) { + val columns = + arrayOf(COLUMN_NAME_TRACKER, COLUMN_NAME_APP_UID) + var selection: String? = null + var selectionArg: Array<String>? = null + if (appUid >= 0) { + selection = "$COLUMN_NAME_APP_UID = ?" + selectionArg = arrayOf("" + appUid) + } + val db = readableDatabase + val cursor = db.query( + true, + TABLE_NAME, + columns, + selection, + selectionArg, + null, + null, + null, + null + ) + val trackers: MutableList<Tracker> = ArrayList() + while (cursor.moveToNext()) { + val trackerId = + cursor.getString(COLUMN_NAME_TRACKER) + val tracker = trackersRepository.getTracker(trackerId) + if (tracker != null) { + trackers.add(tracker) + } + } + cursor.close() + db.close() + return trackers + } + } + + val allTrackers: List<Tracker> + get() = getAllTrackersOfApp(-1) + + class StatEntry { + var app_uid = 0 + var sum_contacted = 0 + var sum_blocked = 0 + var timestamp: Long = 0 + var tracker = 0 + } + + private fun getCurrentHourTs(): Long { + val hourInMs = TimeUnit.HOURS.toMillis(1L) + val hourInS = TimeUnit.HOURS.toSeconds(1L) + return System.currentTimeMillis() / hourInMs * hourInS + } + + private fun getPeriodStartTs( + periodsCount: Int, + periodUnit: TemporalUnit + ): Long { + var start = ZonedDateTime.now() + .minus(periodsCount.toLong(), periodUnit) + .plus(1, periodUnit) + var truncatePeriodUnit = periodUnit + if (periodUnit === ChronoUnit.MONTHS) { + start = start.withDayOfMonth(1) + truncatePeriodUnit = ChronoUnit.DAYS + } + return start.truncatedTo(truncatePeriodUnit).toEpochSecond() + } + + private fun Cursor.getInt(columnName: String): Int { + val columnIndex = getColumnIndex(columnName) + return if (columnIndex >= 0) getInt(columnIndex) else 0 + } + + private fun Cursor.getLong(columnName: String): Long { + val columnIndex = getColumnIndex(columnName) + return if (columnIndex >= 0) getLong(columnIndex) else 0 + } + + private fun Cursor.getString(columnName: String): String { + val columnIndex = getColumnIndex(columnName) + return if (columnIndex >= 0) getString(columnIndex) else "" + } + +}
\ No newline at end of file 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 deleted file mode 100644 index bfe688f..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - 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/StatsRepository.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt new file mode 100644 index 0000000..0e88102 --- /dev/null +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt @@ -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.data + +import android.content.Context +import foundation.e.privacymodules.trackers.api.Tracker +import java.time.temporal.TemporalUnit + +class StatsRepository private constructor(context: Context) { + private val database: StatsDatabase + private var newDataCallback: (() -> Unit)? = null + + companion object { + private var instance: StatsRepository? = null + fun getInstance(context: Context): StatsRepository { + return instance ?: StatsRepository(context).apply { instance = this } + } + } + + init { + database = StatsDatabase(context) + } + + fun setNewDataCallback(callback: () -> Unit) { + newDataCallback = callback + } + + fun logAccess(trackerId: String?, appUid: Int, blocked: Boolean) { + database.logAccess(trackerId, appUid, blocked) + newDataCallback?.invoke() + } + + fun getTrackersCallsOnPeriod( + periodsCount: Int, + periodUnit: TemporalUnit + ): List<Pair<Int, Int>> { + return database.getTrackersCallsOnPeriod(periodsCount, periodUnit) + } + + fun getActiveTrackersByPeriod(periodsCount: Int, periodUnit: TemporalUnit): Int { + return database.getActiveTrackersByPeriod(periodsCount, periodUnit) + } + + fun getContactedTrackersCountByApp(): Map<Int, Int> { + return database.getContactedTrackersCountByApp() + } + + fun getContactedTrackersCount(appUids: List<Int>?): Int { + return database.getContactedTrackersCount(appUids) + } + + fun getAllTrackersOfApp(app_uid: Int): List<Tracker> { + return database.getAllTrackersOfApp(app_uid) + } + + fun getCallsByApps(periodCount: Int, periodUnit: TemporalUnit): Map<Int, Pair<Int, Int>> { + return database.getCallsByApps(periodCount, periodUnit) + } + + fun getCalls(appUid: Int, periodCount: Int, periodUnit: TemporalUnit): Pair<Int, Int> { + return database.getCalls(appUid, periodCount, periodUnit) + } + + fun getMostLeakedApp(periodCount: Int, periodUnit: TemporalUnit): Int { + 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 deleted file mode 100644 index 5c77c7a..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - 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/TrackersRepository.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.kt new file mode 100644 index 0000000..bc4d50b --- /dev/null +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.kt @@ -0,0 +1,58 @@ +/* + 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 foundation.e.privacymodules.trackers.api.Tracker + +class TrackersRepository private constructor() { + private var trackersById: Map<String, Tracker> = HashMap() + private var hostnameToId: Map<String, String> = HashMap() + + companion object { + private var instance: TrackersRepository? = null + fun getInstance(): TrackersRepository { + return instance?: TrackersRepository().apply { instance = this } + } + } + + fun setTrackersList(list: List<Tracker>) { + val trackersById: MutableMap<String, Tracker> = HashMap() + val hostnameToId: MutableMap<String, String> = HashMap() + list.forEach { tracker -> + trackersById[tracker.id] = tracker + for (hostname in tracker.hostnames) { + hostnameToId[hostname] = tracker.id + } + } + this.trackersById = trackersById + this.hostnameToId = hostnameToId + } + + fun isTracker(hostname: String?): Boolean { + return hostnameToId.containsKey(hostname) + } + + fun getTrackerId(hostname: String?): String? { + return hostnameToId[hostname] + } + + fun getTracker(id: String?): Tracker? { + return trackersById[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 deleted file mode 100644 index 9bfca7f..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - 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())); - } -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt new file mode 100644 index 0000000..65a8c39 --- /dev/null +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt @@ -0,0 +1,128 @@ +/* + 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 foundation.e.privacymodules.trackers.api.Tracker + +class WhitelistRepository private constructor(context: Context) { + private lateinit var appsWhitelist: Set<Int> + private val trackersWhitelistByApp: MutableMap<Int, MutableSet<String>> = HashMap() + private val prefs: SharedPreferences + + companion object { + private const val SHARED_PREFS_FILE = "trackers_whitelist.prefs" + private const val KEY_BLOKING_ENABLED = "blocking_enabled" + private const val KEY_APPS_WHITELIST = "apps_whitelist" + private const val KEY_APP_TRACKERS_WHITELIST_PREFIX = "app_trackers_whitelist_" + private var instance: WhitelistRepository? = null + fun getInstance(context: Context): WhitelistRepository { + return instance?: WhitelistRepository(context).apply { instance = this } + } + } + + init { + prefs = context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE) + reloadCache() + } + + private fun reloadCache() { + isBlockingEnabled = prefs.getBoolean(KEY_BLOKING_ENABLED, false) + reloadAppsWhiteList() + reloadAllAppTrackersWhiteList() + } + + private fun reloadAppsWhiteList() { + appsWhitelist = prefs.getStringSet(KEY_APPS_WHITELIST, HashSet())?.mapNotNull { + try { it.toInt() } catch(e: Exception) { null } + }?.toHashSet()?: HashSet() + } + + private fun reloadAppTrackersWhiteList(appUid: Int) { + val key = buildAppTrackersKey(appUid) + trackersWhitelistByApp[appUid] = prefs.getStringSet(key, HashSet())?: HashSet() + } + + private fun reloadAllAppTrackersWhiteList() { + trackersWhitelistByApp.clear() + prefs.all.keys.forEach { key -> + if (key.startsWith(KEY_APP_TRACKERS_WHITELIST_PREFIX)) { + val appUid = key.substring(KEY_APP_TRACKERS_WHITELIST_PREFIX.length).toInt() + reloadAppTrackersWhiteList(appUid) + } + } + } + + + + var isBlockingEnabled: Boolean = false + get() = field + set(enabled) { + prefs.edit().putBoolean(KEY_BLOKING_ENABLED, enabled).apply() + field = enabled + } + + + fun setWhiteListed(appUid: Int, isWhiteListed: Boolean) { + val current = prefs.getStringSet(KEY_APPS_WHITELIST, HashSet())?.toHashSet()?: HashSet() + + if (isWhiteListed) { + current.add("" + appUid) + } else { + current.remove("" + appUid) + } + prefs.edit().putStringSet(KEY_APPS_WHITELIST, current).commit() + reloadAppsWhiteList() + } + + private fun buildAppTrackersKey(appUid: Int): String { + return KEY_APP_TRACKERS_WHITELIST_PREFIX + appUid + } + + fun setWhiteListed(tracker: Tracker, appUid: Int, isWhiteListed: Boolean) { + val trackers = trackersWhitelistByApp.getOrDefault(appUid, HashSet()) + trackersWhitelistByApp[appUid] = trackers + + if (isWhiteListed) { + trackers.add(tracker.id) + } else { + trackers.remove(tracker.id) + } + prefs.edit().putStringSet(buildAppTrackersKey(appUid), trackers).commit() + } + + fun isAppWhiteListed(appUid: Int): Boolean { + return appsWhitelist.contains(appUid) + } + + fun isTrackerWhiteListedForApp(trackerId: String?, appUid: Int): Boolean { + return trackersWhitelistByApp.getOrDefault(appUid, HashSet()).contains(trackerId) + } + + fun areWhiteListEmpty(): Boolean { + return appsWhitelist.isEmpty() && trackersWhitelistByApp.all { (_, trackers) -> trackers.isEmpty() } + } + + val whiteListedApp: List<Int> get() = appsWhitelist.toList() + + fun getWhiteListForApp(appUid: Int): List<String> { + return trackersWhitelistByApp[appUid]?.toList()?: emptyList() + } +}
\ No newline at end of file |