From c8d88ec3364218802bc48257b7766ad8f19a6e45 Mon Sep 17 00:00:00 2001
From: Guillaume Jacquart <guillaume.jacquart@hoodbrains.com>
Date: Wed, 17 Aug 2022 08:49:03 +0000
Subject: 2-Simplify sources modules tree

---
 trackers/build.gradle                              |  45 ++
 trackers/src/main/AndroidManifest.xml              |  37 ++
 .../trackers/DNSBlockerRunnable.java               | 173 +++++++
 .../privacymodules/trackers/DNSBlockerService.java |  86 ++++
 .../privacymodules/trackers/ForegroundStarter.java |  42 ++
 .../e/privacymodules/trackers/TrackersLogger.java  |  83 ++++
 .../trackers/api/BlockTrackersPrivacyModule.java   | 125 +++++
 .../trackers/api/TrackTrackersPrivacyModule.java   | 150 ++++++
 .../trackers/data/StatsDatabase.java               | 507 +++++++++++++++++++++
 .../trackers/data/StatsRepository.java             |  95 ++++
 .../trackers/data/TrackersRepository.java          |  71 +++
 .../trackers/data/WhitelistRepository.java         | 153 +++++++
 12 files changed, 1567 insertions(+)
 create mode 100644 trackers/build.gradle
 create mode 100644 trackers/src/main/AndroidManifest.xml
 create mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.java
 create mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.java
 create mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.java
 create mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.java
 create mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.java
 create mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.java
 create mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.java
 create mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.java
 create mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.java
 create mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.java

(limited to 'trackers')

diff --git a/trackers/build.gradle b/trackers/build.gradle
new file mode 100644
index 0000000..51f8448
--- /dev/null
+++ b/trackers/build.gradle
@@ -0,0 +1,45 @@
+/*
+ Copyright (C) 2022 ECORP
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+    compileSdkVersion buildConfig.compileSdk
+
+    defaultConfig {
+        minSdkVersion buildConfig.minSdk
+        targetSdkVersion buildConfig.targetSdk
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+}
+dependencies{
+    implementation project(":api")
+}
diff --git a/trackers/src/main/AndroidManifest.xml b/trackers/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..debdf61
--- /dev/null
+++ b/trackers/src/main/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright  (C) 2022 ECORP
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="foundation.e.privacymodules.trackers">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+
+
+    <application>
+        <service
+            android:name="foundation.e.privacymodules.trackers.DNSBlockerService"
+            android:enabled="true"
+            android:exported="true" />
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.java
new file mode 100644
index 0000000..80f00c1
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.java
@@ -0,0 +1,173 @@
+/*
+ Copyright (C) 2022 ECORP
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+/*
+ PersonalDNSFilter 1.5
+ Copyright (C) 2017 Ingo Zenz
+ Copyright (C) 2021 ECORP
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+package foundation.e.privacymodules.trackers;
+
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import foundation.e.privacymodules.trackers.data.TrackersRepository;
+import foundation.e.privacymodules.trackers.data.WhitelistRepository;
+
+
+public class DNSBlockerRunnable implements Runnable {
+
+	LocalServerSocket resolverReceiver;
+	boolean stopped = false;
+	private final TrackersLogger trackersLogger;
+	private final TrackersRepository trackersRepository;
+	private final WhitelistRepository whitelistRepository;
+
+	private int eBrowserAppUid = -1;
+
+	private final String TAG = DNSBlockerRunnable.class.getName();
+	private static final String SOCKET_NAME = "foundation.e.advancedprivacy";
+
+
+	public DNSBlockerRunnable(Context ct, TrackersLogger trackersLogger, TrackersRepository trackersRepository, WhitelistRepository whitelistRepository) {
+		this.trackersLogger = trackersLogger;
+		this.trackersRepository = trackersRepository;
+		this.whitelistRepository = whitelistRepository;
+		initEBrowserDoTFix(ct);
+	}
+
+	public synchronized void stop() {
+		stopped = true;
+		closeSocket();
+	}
+
+	private void closeSocket() {
+		// Known bug and workaround that LocalServerSocket::close is not working well
+		// https://issuetracker.google.com/issues/36945762
+		if (resolverReceiver != null) {
+			try {
+				Os.shutdown(resolverReceiver.getFileDescriptor(), OsConstants.SHUT_RDWR);
+				resolverReceiver.close();
+				resolverReceiver = null;
+			} catch (ErrnoException e) {
+				if (e.errno != OsConstants.EBADF) {
+					Log.w(TAG, "Socket already closed");
+				} else {
+					Log.e(TAG, "Exception: cannot close DNS port on stop" + SOCKET_NAME + "!", e);
+				}
+			} catch (Exception e) {
+				Log.e(TAG, "Exception: cannot close DNS port on stop" + SOCKET_NAME + "!", e);
+			}
+		}
+	}
+
+	@Override
+	public void run() {
+		try {
+			resolverReceiver = new LocalServerSocket(SOCKET_NAME);
+		} catch (IOException eio) {
+			Log.e(TAG, "Exception:Cannot open DNS port " + SOCKET_NAME + "!", eio);
+			return;
+		}
+		Log.d(TAG, "DNSFilterProxy running on port " + SOCKET_NAME + "!");
+
+		while (!stopped) {
+			try {
+				LocalSocket socket = resolverReceiver.accept();
+
+				BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+				String line = reader.readLine();
+				String[] params = line.split(",");
+				OutputStream output = socket.getOutputStream();
+				PrintWriter writer = new PrintWriter(output, true);
+
+				String domainName = params[0];
+				int appUid = Integer.parseInt(params[1]);
+				boolean isBlocked = false;
+
+				if (isEBrowserDoTBlockFix(appUid, domainName)) {
+					isBlocked = true;
+				} else if (trackersRepository.isTracker(domainName)) {
+					String trackerId = trackersRepository.getTrackerId(domainName);
+
+					if (shouldBlock(appUid, trackerId)) {
+							writer.println("block");
+							isBlocked = true;
+					}
+					trackersLogger.logAccess(trackerId, appUid, isBlocked);
+				}
+
+				if (!isBlocked) {
+					writer.println("pass");
+				}
+				socket.close();
+				// Printing bufferedreader data
+			} catch (IOException e) {
+				Log.w(TAG, "Exception while listening DNS resolver", e);
+			}
+		}
+	}
+
+	private void initEBrowserDoTFix(Context context) {
+		try {
+			eBrowserAppUid = context.getPackageManager().getApplicationInfo("foundation.e.browser", 0).uid;
+		} catch (PackageManager.NameNotFoundException e) {
+			Log.i(TAG, "no E Browser package found.");
+		}
+	}
+
+	private static final String E_BROWSER_DOT_SERVER = "chrome.cloudflare-dns.com";
+	private boolean isEBrowserDoTBlockFix(int appUid, String hostname) {
+		return appUid == eBrowserAppUid && E_BROWSER_DOT_SERVER.equals(hostname);
+	}
+
+	private boolean shouldBlock(int appUid, String trackerId) {
+		return whitelistRepository.isBlockingEnabled() &&
+			!whitelistRepository.isAppWhiteListed(appUid) &&
+			!whitelistRepository.isTrackerWhiteListedForApp(trackerId, appUid);
+	}
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.java
new file mode 100644
index 0000000..6250621
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.java
@@ -0,0 +1,86 @@
+/*
+ Copyright (C) 2021 ECORP
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+
+package foundation.e.privacymodules.trackers;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import foundation.e.privacymodules.trackers.data.TrackersRepository;
+import foundation.e.privacymodules.trackers.data.WhitelistRepository;
+
+public class DNSBlockerService extends Service {
+    private static final String TAG = "DNSBlockerService";
+    private static DNSBlockerRunnable sDNSBlocker;
+    private TrackersLogger trackersLogger;
+
+    public static final String ACTION_START = "foundation.e.privacymodules.trackers.intent.action.START";
+
+    public static final String EXTRA_ENABLE_NOTIFICATION = "foundation.e.privacymodules.trackers.intent.extra.ENABLED_NOTIFICATION";
+
+    public DNSBlockerService() {
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        // TODO: Return the communication channel to the service.
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent != null && intent.getBooleanExtra(EXTRA_ENABLE_NOTIFICATION, true)) {
+            ForegroundStarter.startForeground(this);
+        }
+
+        if (intent != null && ACTION_START.equals(intent.getAction())) {
+            stop();
+            start();
+        }
+
+        return START_STICKY;
+    }
+
+    private void start() {
+        try {
+            trackersLogger = new TrackersLogger(this);
+            sDNSBlocker = new DNSBlockerRunnable(this, trackersLogger,
+                    TrackersRepository.getInstance(), WhitelistRepository.getInstance(this));
+
+            new Thread(sDNSBlocker).start();
+        } catch(Exception e) {
+            Log.e(TAG, "Error while starting DNSBlocker service", e);
+            stop();
+        }
+    }
+
+    private void stop() {
+        if (sDNSBlocker != null) {
+            sDNSBlocker.stop();
+        }
+        sDNSBlocker = null;
+        if (trackersLogger != null) {
+            trackersLogger.stop();
+        }
+        trackersLogger = null;
+    }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.java
new file mode 100644
index 0000000..1563163
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.java
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2021 ECORP
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+package foundation.e.privacymodules.trackers;
+
+import static android.content.Context.NOTIFICATION_SERVICE;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.os.Build;
+
+
+public class ForegroundStarter {
+    private static final String NOTIFICATION_CHANNEL_ID = "blocker_service";
+    public static void startForeground(Service service){
+        NotificationManager mNotificationManager = (NotificationManager) service.getSystemService(NOTIFICATION_SERVICE);
+        if (Build.VERSION.SDK_INT >= 26) {
+            mNotificationManager.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW));
+            Notification notification = new Notification.Builder(service, NOTIFICATION_CHANNEL_ID)
+                    .setContentTitle("Trackers filter").build();
+            service.startForeground(1337, notification);
+        }
+    }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.java
new file mode 100644
index 0000000..3710253
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.java
@@ -0,0 +1,83 @@
+/*
+ Copyright (C) 2022 ECORP
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+package foundation.e.privacymodules.trackers;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+import foundation.e.privacymodules.trackers.data.StatsRepository;
+
+
+public class TrackersLogger {
+    private static final String TAG = "TrackerModule";
+    private StatsRepository statsRepository;
+
+    private LinkedBlockingQueue<DetectedTracker> queue;
+    private boolean stopped = false;
+
+
+    public TrackersLogger(Context context) {
+        statsRepository = StatsRepository.getInstance(context);
+        queue = new LinkedBlockingQueue<DetectedTracker>();
+        startWriteLogLoop();
+    }
+
+    public void stop() {
+        stopped = true;
+    }
+
+    public void logAccess(String trackerId, int appId, boolean wasBlocked) {
+        queue.offer(new DetectedTracker(trackerId, appId, wasBlocked));
+    }
+
+    private void startWriteLogLoop() {
+        Runnable writeLogRunner = new Runnable() {
+            @Override
+            public void run() {
+                while(!stopped) {
+                    try {
+                        logAccess(queue.take());
+                    } catch (InterruptedException e) {
+                        Log.e(TAG, "writeLogLoop detectedTrackersQueue.take() interrupted: ", e);
+                    }
+                }
+            }
+        };
+        new Thread(writeLogRunner).start();
+    }
+
+
+    public void logAccess(DetectedTracker detectedTracker) {
+        statsRepository.logAccess(detectedTracker.trackerId, detectedTracker.appUid, detectedTracker.wasBlocked);
+    }
+
+    private class DetectedTracker {
+        String trackerId;
+        int appUid;
+        boolean wasBlocked;
+
+        public DetectedTracker(String trackerId, int appUid, boolean wasBlocked) {
+            this.trackerId = trackerId;
+            this.appUid = appUid;
+            this.wasBlocked = wasBlocked;
+        }
+    }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.java
new file mode 100644
index 0000000..ea62766
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.java
@@ -0,0 +1,125 @@
+/*
+ Copyright (C) 2021 ECORP
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+
+package foundation.e.privacymodules.trackers.api;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import foundation.e.privacymodules.permissions.data.ApplicationDescription;
+import foundation.e.privacymodules.trackers.IBlockTrackersPrivacyModule;
+import foundation.e.privacymodules.trackers.Tracker;
+import foundation.e.privacymodules.trackers.data.TrackersRepository;
+import foundation.e.privacymodules.trackers.data.WhitelistRepository;
+
+public class BlockTrackersPrivacyModule implements IBlockTrackersPrivacyModule {
+
+    private final Context mContext;
+    private List<Listener> mListeners = new ArrayList<>();
+    private static BlockTrackersPrivacyModule sBlockTrackersPrivacyModule;
+
+    private TrackersRepository trackersRepository;
+    private WhitelistRepository whitelistRepository;
+
+    public BlockTrackersPrivacyModule(Context context) {
+        mContext = context;
+        trackersRepository = TrackersRepository.getInstance();
+        whitelistRepository = WhitelistRepository.getInstance(mContext);
+    }
+
+    public static BlockTrackersPrivacyModule getInstance(Context ct){
+        if(sBlockTrackersPrivacyModule == null){
+            sBlockTrackersPrivacyModule = new BlockTrackersPrivacyModule(ct);
+        }
+        return sBlockTrackersPrivacyModule;
+    }
+
+    @Override
+    public void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void clearListeners() {
+        mListeners.clear();
+    }
+
+    @Override
+    public void disableBlocking() {
+        whitelistRepository.setBlockingEnabled(false);
+        for(Listener listener:mListeners){
+            listener.onBlockingToggle(false);
+        }
+    }
+
+    @Override
+    public void enableBlocking() {
+        whitelistRepository.setBlockingEnabled(true);
+        for(Listener listener:mListeners){
+            listener.onBlockingToggle(true);
+        }
+    }
+
+    @Override
+    public List<Tracker> getWhiteList(int appUid) {
+        List<Tracker> trackers = new ArrayList();
+        for (String trackerId: whitelistRepository.getWhiteListForApp(appUid)) {
+            trackers.add(trackersRepository.getTracker(trackerId));
+        }
+        return trackers;
+    }
+
+    @Override
+    public List<Integer> getWhiteListedApp() {
+        return whitelistRepository.getWhiteListedApp();
+    }
+
+    @Override
+    public boolean isBlockingEnabled() {
+        return whitelistRepository.isBlockingEnabled();
+    }
+
+    @Override
+    public boolean isWhiteListEmpty() {
+        return whitelistRepository.areWhiteListEmpty();
+    }
+
+    @Override
+    public boolean isWhitelisted(int appUid) {
+        return whitelistRepository.isAppWhiteListed(appUid);
+    }
+
+    @Override
+    public void removeListener(Listener listener) {
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public void setWhiteListed(Tracker tracker, int appUid, boolean isWhiteListed) {
+        whitelistRepository.setWhiteListed(tracker, appUid, isWhiteListed);
+    }
+
+    @Override
+    public void setWhiteListed(int appUid, boolean isWhiteListed) {
+        whitelistRepository.setWhiteListed(appUid, isWhiteListed);
+    }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.java
new file mode 100644
index 0000000..38b2c8f
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.java
@@ -0,0 +1,150 @@
+/*
+ Copyright (C) 2021 ECORP
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+
+package foundation.e.privacymodules.trackers.api;
+
+import android.content.Context;
+import android.content.Intent;
+
+
+import org.jetbrains.annotations.NotNull;
+
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import foundation.e.privacymodules.trackers.DNSBlockerService;
+import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule;
+import foundation.e.privacymodules.trackers.Tracker;
+import foundation.e.privacymodules.trackers.data.StatsRepository;
+import foundation.e.privacymodules.trackers.data.TrackersRepository;
+import kotlin.Pair;
+
+public class TrackTrackersPrivacyModule implements ITrackTrackersPrivacyModule {
+
+    private static TrackTrackersPrivacyModule sTrackTrackersPrivacyModule;
+    private final Context mContext;
+    private StatsRepository statsRepository;
+    private List<ITrackTrackersPrivacyModule.Listener> mListeners = new ArrayList();
+
+    public TrackTrackersPrivacyModule(Context context) {
+        mContext = context;
+        statsRepository = StatsRepository.getInstance(context);
+        statsRepository.setNewDataCallback((newData) -> {
+            mListeners.forEach((listener) -> { listener.onNewData(); });
+        });
+    }
+
+    public static TrackTrackersPrivacyModule getInstance(Context context){
+        if(sTrackTrackersPrivacyModule == null){
+            sTrackTrackersPrivacyModule = new TrackTrackersPrivacyModule(context);
+        }
+        return sTrackTrackersPrivacyModule;
+    }
+
+    public void start(List<Tracker> trackers, boolean enableNotification) {
+        TrackersRepository.getInstance().setTrackersList(trackers);
+
+        Intent intent = new Intent(mContext, DNSBlockerService.class);
+        intent.setAction(DNSBlockerService.ACTION_START);
+        intent.putExtra(DNSBlockerService.EXTRA_ENABLE_NOTIFICATION, enableNotification);
+        mContext.startService(intent);
+    }
+
+    @NotNull
+    @Override
+    public List<Pair<Integer, Integer>> getPastDayTrackersCalls() {
+        return statsRepository.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS);
+    }
+
+    @Override
+    public List<Pair<Integer, Integer>> getPastMonthTrackersCalls() {
+        return statsRepository.getTrackersCallsOnPeriod(30, ChronoUnit.DAYS);
+    }
+
+    @Override
+    public List<Pair<Integer, Integer>> getPastYearTrackersCalls() {
+        return statsRepository.getTrackersCallsOnPeriod(12, ChronoUnit.MONTHS);
+    }
+
+    @Override
+    public int getTrackersCount() {
+        return statsRepository.getContactedTrackersCount();
+    }
+
+    @Override
+    public Map<Integer, Integer> getTrackersCountByApp() {
+        return statsRepository.getContactedTrackersCountByApp();
+    }
+
+    @Override
+    public List<Tracker> getTrackersForApp(int i) {
+        return statsRepository.getAllTrackersOfApp(i);
+    }
+
+
+    @Override
+    public int getPastDayTrackersCount() {
+        return statsRepository.getActiveTrackersByPeriod(24, ChronoUnit.HOURS);
+    }
+
+    @Override
+    public int getPastMonthTrackersCount() {
+        return statsRepository.getActiveTrackersByPeriod(30, ChronoUnit.DAYS);
+    }
+
+    @Override
+    public int getPastYearTrackersCount() {
+        return statsRepository.getActiveTrackersByPeriod(12, ChronoUnit.MONTHS);
+    }
+
+    @Override
+    public int getPastDayMostLeakedApp() {
+        return statsRepository.getMostLeakedApp(24, ChronoUnit.HOURS);
+    }
+
+    @NotNull
+    @Override
+    public Map<Integer, Pair<Integer, Integer>> getPastDayTrackersCallsByApps() {
+        return statsRepository.getCallsByApps(24, ChronoUnit.HOURS);
+    }
+
+    @NotNull
+    @Override
+    public Pair<Integer, Integer> getPastDayTrackersCallsForApp(int appUid) {
+        return statsRepository.getCalls(appUid, 24, ChronoUnit.HOURS);
+    }
+
+    @Override
+    public void addListener(ITrackTrackersPrivacyModule.Listener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void removeListener(ITrackTrackersPrivacyModule.Listener listener) {
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public void clearListeners() {
+        mListeners.clear();
+    }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.java
new file mode 100644
index 0000000..0650114
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.java
@@ -0,0 +1,507 @@
+/*
+ Copyright (C) 2021 ECORP
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+
+package foundation.e.privacymodules.trackers.data;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import foundation.e.privacymodules.trackers.Tracker;
+import kotlin.Pair;
+
+public class StatsDatabase extends SQLiteOpenHelper {
+    public static final int DATABASE_VERSION = 1;
+    public static final String DATABASE_NAME = "TrackerFilterStats.db";
+    private final Object lock = new Object();
+    private TrackersRepository trackersRepository;
+
+    public StatsDatabase(Context context) {
+        super(context, DATABASE_NAME, null, DATABASE_VERSION);
+        trackersRepository = TrackersRepository.getInstance();
+    }
+
+    public void onCreate(SQLiteDatabase db) {
+        db.execSQL(SQL_CREATE_TABLE);
+    }
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        onCreate(db);
+    }
+
+
+    public static class AppTrackerEntry implements BaseColumns {
+        public static final String TABLE_NAME = "tracker_filter_stats";
+        public static final String COLUMN_NAME_TIMESTAMP = "timestamp";
+        public static final String COLUMN_NAME_TRACKER = "tracker";
+        public static final String COLUMN_NAME_APP_UID = "app_uid";
+        public static final String COLUMN_NAME_NUMBER_CONTACTED = "sum_contacted";
+        public static final String COLUMN_NAME_NUMBER_BLOCKED = "sum_blocked";
+
+    }
+
+    String[] projection = {
+            AppTrackerEntry.COLUMN_NAME_TIMESTAMP,
+            AppTrackerEntry.COLUMN_NAME_APP_UID,
+            AppTrackerEntry.COLUMN_NAME_TRACKER,
+            AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED,
+            AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED
+    };
+
+    private static final String SQL_CREATE_TABLE =
+            "CREATE TABLE " + AppTrackerEntry.TABLE_NAME + " (" +
+                    AppTrackerEntry._ID + " INTEGER PRIMARY KEY," +
+                    AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " INTEGER,"+
+                    AppTrackerEntry.COLUMN_NAME_APP_UID + " INTEGER," +
+                    AppTrackerEntry.COLUMN_NAME_TRACKER + " TEXT," +
+                    AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED + " INTEGER," +
+                    AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED + " INTEGER)";
+
+    private static final String PROJECTION_NAME_PERIOD = "period";
+    private static final String PROJECTION_NAME_CONTACTED_SUM = "contactedsum";
+    private static final String PROJECTION_NAME_BLOCKED_SUM = "blockedsum";
+    private static final String PROJECTION_NAME_LEAKED_SUM = "leakedsum";
+    private static final String PROJECTION_NAME_TRACKERS_COUNT = "trackerscount";
+
+    private HashMap<String, Pair<Integer, Integer>> getCallsByPeriod(
+        int periodsCount,
+        TemporalUnit periodUnit,
+        String sqlitePeriodFormat
+    ) {
+        synchronized (lock) {
+            long minTimestamp = getPeriodStartTs(periodsCount, periodUnit);
+
+            SQLiteDatabase db = getReadableDatabase();
+
+            String selection = AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " >= ?";
+            String[] selectionArg = new String[]{"" + minTimestamp};
+            String projection =
+                    AppTrackerEntry.COLUMN_NAME_TIMESTAMP + ", " +
+                            "STRFTIME('" + sqlitePeriodFormat + "', DATETIME(" + AppTrackerEntry.COLUMN_NAME_TIMESTAMP + ", 'unixepoch', 'localtime')) " + PROJECTION_NAME_PERIOD + "," +
+                            "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED + ") " + PROJECTION_NAME_CONTACTED_SUM + "," +
+                            "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED + ") " + PROJECTION_NAME_BLOCKED_SUM;
+            Cursor cursor = db.rawQuery("SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME
+                    + " WHERE " + selection +
+                    " GROUP BY " + PROJECTION_NAME_PERIOD +
+                    " ORDER BY " + AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " DESC" +
+                    " LIMIT " + periodsCount, selectionArg);
+
+            HashMap<String, Pair<Integer, Integer>> callsByPeriod = new HashMap<>();
+            while (cursor.moveToNext()) {
+                int contacted = cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_CONTACTED_SUM));
+                int blocked = cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_BLOCKED_SUM));
+
+                callsByPeriod.put(
+                        cursor.getString(cursor.getColumnIndex(PROJECTION_NAME_PERIOD)),
+                        new Pair(blocked, contacted - blocked)
+                );
+            }
+
+            cursor.close();
+            db.close();
+
+            return callsByPeriod;
+        }
+    }
+
+    private List<Pair<Integer, Integer>> callsByPeriodToPeriodsList(
+        Map<String, Pair<Integer, Integer>> callsByPeriod,
+        int periodsCount,
+        TemporalUnit periodUnit,
+        String javaPeriodFormat
+    ) {
+        ZonedDateTime currentDate = ZonedDateTime.now().minus(periodsCount, periodUnit);
+        DateTimeFormatter formater = DateTimeFormatter.ofPattern(javaPeriodFormat);
+
+        List<Pair<Integer, Integer>> calls = new ArrayList(periodsCount);
+        for (int i = 0; i < periodsCount; i++) {
+            currentDate = currentDate.plus(1, periodUnit);
+
+            String currentPeriod = formater.format(currentDate);
+            calls.add(callsByPeriod.getOrDefault(currentPeriod, new Pair(0, 0)));
+        }
+        return calls;
+    }
+
+    public List<Pair<Integer, Integer>> getTrackersCallsOnPeriod(int periodsCount, TemporalUnit periodUnit) {
+        String sqlitePeriodFormat = "%Y%m";
+        String javaPeriodFormat = "yyyyMM";
+
+        if (periodUnit == ChronoUnit.MONTHS) {
+            sqlitePeriodFormat = "%Y%m";
+            javaPeriodFormat = "yyyyMM";
+        } else if (periodUnit == ChronoUnit.DAYS) {
+            sqlitePeriodFormat = "%Y%m%d";
+            javaPeriodFormat = "yyyyMMdd";
+        } else if (periodUnit == ChronoUnit.HOURS) {
+            sqlitePeriodFormat = "%Y%m%d%H";
+            javaPeriodFormat = "yyyyMMddHH";
+        }
+
+        Map<String, Pair<Integer, Integer>> callsByPeriod = getCallsByPeriod(periodsCount, periodUnit, sqlitePeriodFormat);
+        return callsByPeriodToPeriodsList(callsByPeriod, periodsCount, periodUnit, javaPeriodFormat);
+    }
+
+
+
+    public int getActiveTrackersByPeriod(int periodsCount, TemporalUnit periodUnit) {
+        synchronized (lock) {
+            long minTimestamp = getPeriodStartTs(periodsCount, periodUnit);
+
+
+            SQLiteDatabase db = getWritableDatabase();
+
+            String selection = AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " >= ? AND " +
+                    AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED + " > " + AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED;
+            String[] selectionArg = new String[]{"" + minTimestamp};
+            String projection =
+                    "COUNT(DISTINCT " + AppTrackerEntry.COLUMN_NAME_TRACKER + ") " + PROJECTION_NAME_TRACKERS_COUNT;
+            Cursor cursor = db.rawQuery("SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME
+                    + " WHERE " + selection, selectionArg);
+
+            int count = 0;
+
+            if (cursor.moveToNext()) {
+                count = cursor.getInt(0);
+            }
+
+            cursor.close();
+            db.close();
+
+            return count;
+        }
+
+    }
+
+    public int getContactedTrackersCount() {
+        synchronized (lock) {
+            SQLiteDatabase db = getReadableDatabase();
+            String projection =
+                    "COUNT(DISTINCT " + AppTrackerEntry.COLUMN_NAME_TRACKER + ") " + PROJECTION_NAME_TRACKERS_COUNT;
+
+            Cursor cursor = db.rawQuery(
+                    "SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME,
+                    new String[]{});
+
+            int count = 0;
+
+            if (cursor.moveToNext()) {
+                count = cursor.getInt(0);
+            }
+
+            cursor.close();
+            db.close();
+
+            return count;
+        }
+    }
+
+
+    public Map<Integer, Integer> getContactedTrackersCountByApp() {
+        synchronized (lock) {
+            SQLiteDatabase db = getReadableDatabase();
+            String projection =
+                    AppTrackerEntry.COLUMN_NAME_APP_UID + ", " +
+                    "COUNT(DISTINCT " + AppTrackerEntry.COLUMN_NAME_TRACKER + ") " + PROJECTION_NAME_TRACKERS_COUNT;
+
+            Cursor cursor = db.rawQuery(
+                "SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME +
+                " GROUP BY " + AppTrackerEntry.COLUMN_NAME_APP_UID,
+                new String[]{});
+
+            HashMap<Integer, Integer> countByApp = new HashMap();
+
+            while (cursor.moveToNext()) {
+                countByApp.put(
+                        cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_APP_UID)),
+                        cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_TRACKERS_COUNT))
+                );
+            }
+
+            cursor.close();
+            db.close();
+
+            return countByApp;
+        }
+    }
+
+    public Map<Integer, Pair<Integer, Integer>> getCallsByApps(int periodCount, TemporalUnit periodUnit) {
+        synchronized (lock) {
+            long minTimestamp = getPeriodStartTs(periodCount, periodUnit);
+
+            SQLiteDatabase db = getReadableDatabase();
+
+            String selection = AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " >= ?";
+            String[] selectionArg = new String[]{"" + minTimestamp};
+            String projection =
+                    AppTrackerEntry.COLUMN_NAME_APP_UID + ", " +
+                    "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED + ") " + PROJECTION_NAME_CONTACTED_SUM + "," +
+                            "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED + ") " + PROJECTION_NAME_BLOCKED_SUM;
+
+            Cursor cursor = db.rawQuery(
+            "SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME +
+                " WHERE " + selection +
+                " GROUP BY " + AppTrackerEntry.COLUMN_NAME_APP_UID,
+                selectionArg);
+
+
+            HashMap<Integer, Pair<Integer, Integer>> callsByApp = new HashMap<>();
+
+            while (cursor.moveToNext()) {
+                int contacted = cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_CONTACTED_SUM));
+                int blocked = cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_BLOCKED_SUM));
+
+                callsByApp.put(
+                    cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_APP_UID)),
+                    new Pair(blocked, contacted - blocked)
+                );
+            }
+
+            cursor.close();
+            db.close();
+
+            return callsByApp;
+        }
+    }
+
+    public Pair<Integer, Integer> getCalls(int appUid, int periodCount, TemporalUnit periodUnit) {
+        synchronized (lock) {
+            long minTimestamp = getPeriodStartTs(periodCount, periodUnit);
+
+            SQLiteDatabase db = getReadableDatabase();
+
+            String selection =
+                    AppTrackerEntry.COLUMN_NAME_APP_UID + " = ? AND " +
+                    AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " >= ?";
+            String[] selectionArg = new String[]{ "" + appUid, "" + minTimestamp };
+            String projection =
+                    "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED + ") " + PROJECTION_NAME_CONTACTED_SUM + "," +
+                    "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED + ") " + PROJECTION_NAME_BLOCKED_SUM;
+
+            Cursor cursor = db.rawQuery(
+                "SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME +
+                    " WHERE " + selection,
+                    selectionArg);
+
+            HashMap<Integer, Pair<Integer, Integer>> callsByApp = new HashMap<>();
+
+            Pair<Integer, Integer> calls = new Pair(0, 0);
+
+            if (cursor.moveToNext()) {
+                int contacted = cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_CONTACTED_SUM));
+                int blocked = cursor.getInt(cursor.getColumnIndex(PROJECTION_NAME_BLOCKED_SUM));
+
+                calls = new Pair(blocked, contacted - blocked);
+            }
+
+            cursor.close();
+            db.close();
+
+            return calls;
+        }
+    }
+
+    public int getMostLeakedApp(int periodCount, TemporalUnit periodUnit) {
+        synchronized (lock) {
+            long minTimestamp = getPeriodStartTs(periodCount, periodUnit);
+
+            SQLiteDatabase db = getReadableDatabase();
+
+            String selection = AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " >= ?";
+            String[] selectionArg = new String[]{"" + minTimestamp};
+            String projection =
+                    AppTrackerEntry.COLUMN_NAME_APP_UID + ", " +
+                            "SUM(" + AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED +
+                                " - " + AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED +
+                            ") " + PROJECTION_NAME_LEAKED_SUM;
+
+            Cursor cursor = db.rawQuery(
+            "SELECT " + projection + " FROM " + AppTrackerEntry.TABLE_NAME +
+                " WHERE " + selection +
+                " GROUP BY " + AppTrackerEntry.COLUMN_NAME_APP_UID +
+                " ORDER BY " + PROJECTION_NAME_LEAKED_SUM + " DESC LIMIT 1",
+                selectionArg);
+
+
+            int appUid = 0;
+            if (cursor.moveToNext()) {
+                appUid = cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_APP_UID));
+            }
+
+            cursor.close();
+            db.close();
+
+            return appUid;
+        }
+    }
+
+    private long getCurrentHourTs() {
+        long hourInMs = TimeUnit.HOURS.toMillis(1L);
+        long hourInS = TimeUnit.HOURS.toSeconds(1L);
+        return (System.currentTimeMillis() / hourInMs) * hourInS;
+    }
+
+    private long getPeriodStartTs(
+            int periodsCount,
+            TemporalUnit periodUnit
+    ) {
+
+        ZonedDateTime start = ZonedDateTime.now()
+                .minus(periodsCount, periodUnit)
+                .plus(1, periodUnit);
+
+        TemporalUnit truncatePeriodUnit = periodUnit;
+        if (periodUnit == ChronoUnit.MONTHS) {
+            start = start.withDayOfMonth(1);
+            truncatePeriodUnit = ChronoUnit.DAYS;
+        }
+
+        return start.truncatedTo(truncatePeriodUnit).toEpochSecond();
+    }
+
+    public void logAccess(String trackerId, int appUid, boolean blocked){
+        synchronized (lock) {
+            long currentHour = getCurrentHourTs();
+            SQLiteDatabase db = getWritableDatabase();
+            ContentValues values = new ContentValues();
+            values.put(AppTrackerEntry.COLUMN_NAME_APP_UID, appUid);
+            values.put(AppTrackerEntry.COLUMN_NAME_TRACKER, trackerId);
+            values.put(AppTrackerEntry.COLUMN_NAME_TIMESTAMP, currentHour);
+
+        /*String query = "UPDATE product SET "+AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED+" = "+AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED+" + 1 ";
+        if(blocked)
+            query+=AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED+" = "+AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED+" + 1 ";
+*/
+            String selection =
+                    AppTrackerEntry.COLUMN_NAME_TIMESTAMP + " = ? AND " +
+                    AppTrackerEntry.COLUMN_NAME_APP_UID + " = ? AND " +
+                    AppTrackerEntry.COLUMN_NAME_TRACKER + " = ? ";
+
+            String[] selectionArg = new String[]{"" + currentHour, "" + appUid, trackerId};
+
+            Cursor cursor = db.query(
+                    AppTrackerEntry.TABLE_NAME,
+                    projection,
+                    selection,
+                    selectionArg,
+                    null,
+                    null,
+                    null
+            );
+            if (cursor.getCount() > 0) {
+                cursor.moveToFirst();
+                StatEntry entry = cursorToEntry(cursor);
+                if (blocked)
+                    values.put(AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED, entry.sum_blocked + 1);
+                else
+                    values.put(AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED, entry.sum_blocked);
+                values.put(AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED, entry.sum_contacted + 1);
+                db.update(AppTrackerEntry.TABLE_NAME, values, selection, selectionArg);
+
+                // db.execSQL(query, new String[]{""+hour, ""+day, ""+month, ""+year, ""+appUid, ""+trackerId});
+            } else {
+
+                if (blocked)
+                    values.put(AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED, 1);
+                else
+                    values.put(AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED, 0);
+                values.put(AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED, 1);
+
+
+                long newRowId = db.insert(AppTrackerEntry.TABLE_NAME, null, values);
+            }
+
+            cursor.close();
+            db.close();
+        }
+    }
+
+
+    private StatEntry cursorToEntry(Cursor cursor){
+        StatEntry entry = new StatEntry();
+        entry.timestamp = cursor.getLong(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_TIMESTAMP));
+        entry.app_uid = cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_APP_UID));
+        entry.sum_blocked = cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED));
+        entry.sum_contacted = cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED));
+        entry.tracker = cursor.getInt(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_TRACKER));
+        return entry;
+    }
+
+    public List<Tracker> getAllTrackersOfApp(int appUid){
+        synchronized (lock) {
+            String[] columns = { AppTrackerEntry.COLUMN_NAME_TRACKER, AppTrackerEntry.COLUMN_NAME_APP_UID };
+            String selection = null;
+            String[] selectionArg = null;
+            if (appUid >= 0) {
+                selection = AppTrackerEntry.COLUMN_NAME_APP_UID + " = ?";
+                selectionArg = new String[]{"" + appUid};
+            }
+            SQLiteDatabase db = getReadableDatabase();
+            Cursor cursor = db.query(
+                    true,
+                    AppTrackerEntry.TABLE_NAME,
+                    columns,
+                    selection,
+                    selectionArg,
+                    null,
+                    null,
+                    null,
+                    null
+            );
+            List<Tracker> trackers = new ArrayList<>();
+
+            while (cursor.moveToNext()) {
+                String trackerId = cursor.getString(cursor.getColumnIndex(AppTrackerEntry.COLUMN_NAME_TRACKER));
+                Tracker tracker = trackersRepository.getTracker(trackerId);
+
+                if (tracker != null) {
+                    trackers.add(tracker);
+                }
+            }
+            cursor.close();
+            db.close();
+            return trackers;
+        }
+    }
+
+    public List<Tracker> getAllTrackers(){
+        return getAllTrackersOfApp(-1);
+    }
+
+    public static class StatEntry {
+        int app_uid;
+        int sum_contacted;
+        int sum_blocked;
+        long timestamp;
+        int tracker;
+    }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.java
new file mode 100644
index 0000000..bfe688f
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.java
@@ -0,0 +1,95 @@
+/*
+ Copyright (C) 2022 ECORP
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+package foundation.e.privacymodules.trackers.data;
+
+import android.content.Context;
+
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import foundation.e.privacymodules.trackers.Tracker;
+import kotlin.Pair;
+
+public class StatsRepository {
+    private static StatsRepository instance;
+
+    private StatsDatabase database;
+
+    private Consumer<Boolean> newDataCallback = null;
+
+    private StatsRepository(Context context) {
+        database = new StatsDatabase(context);
+    }
+
+    public static StatsRepository getInstance(Context context) {
+        if (instance == null) {
+            instance = new StatsRepository(context);
+        }
+        return instance;
+    }
+
+    public void setNewDataCallback(Consumer<Boolean> callback) {
+        newDataCallback = callback;
+    }
+
+    public void logAccess(String trackerId, int appUid, boolean blocked) {
+        database.logAccess(trackerId, appUid, blocked);
+        if (newDataCallback != null) {
+            newDataCallback.accept(true);
+        }
+    }
+
+    public List<Pair<Integer, Integer>> getTrackersCallsOnPeriod(int periodsCount, TemporalUnit periodUnit) {
+        return database.getTrackersCallsOnPeriod(periodsCount, periodUnit);
+    }
+
+    public int getActiveTrackersByPeriod(int periodsCount, TemporalUnit periodUnit) {
+        return database.getActiveTrackersByPeriod(periodsCount, periodUnit);
+    }
+
+    public Map<Integer, Integer> getContactedTrackersCountByApp() {
+        return database.getContactedTrackersCountByApp();
+    }
+
+    public int getContactedTrackersCount() {
+        return database.getContactedTrackersCount();
+    }
+
+    public List<Tracker> getAllTrackersOfApp(int app_uid) {
+        return database.getAllTrackersOfApp(app_uid);
+    }
+
+    public Map<Integer, Pair<Integer, Integer>> getCallsByApps(int periodCount, TemporalUnit periodUnit) {
+        return database.getCallsByApps(periodCount, periodUnit);
+    }
+
+    public Pair<Integer, Integer> getCalls(int appUid, int periodCount, TemporalUnit periodUnit) {
+        return database.getCalls(appUid, periodCount, periodUnit);
+    }
+
+
+        public int getMostLeakedApp(int periodCount, TemporalUnit periodUnit) {
+        return database.getMostLeakedApp(periodCount, periodUnit);
+    }
+}
\ No newline at end of file
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.java
new file mode 100644
index 0000000..5c77c7a
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.java
@@ -0,0 +1,71 @@
+/*
+ Copyright (C) 2022 ECORP
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+package foundation.e.privacymodules.trackers.data;
+
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import foundation.e.privacymodules.trackers.Tracker;
+
+public class TrackersRepository {
+    private static TrackersRepository instance;
+
+    private TrackersRepository() { }
+
+    public static TrackersRepository getInstance() {
+        if (instance == null) {
+            instance = new TrackersRepository();
+        }
+        return instance;
+    }
+
+    private Map<String, Tracker> trackersById = new HashMap();
+    private Map<String, String> hostnameToId = new HashMap();
+
+    public void setTrackersList(List<Tracker> list) {
+        Map<String, Tracker> trackersById = new HashMap();
+        Map<String, String> hostnameToId = new HashMap();
+
+        for (Tracker tracker: list) {
+            trackersById.put(tracker.getId(), tracker);
+
+            for (String hostname: tracker.getHostnames()) {
+                hostnameToId.put(hostname, tracker.getId());
+            }
+        }
+
+        this.trackersById = trackersById;
+        this.hostnameToId = hostnameToId;
+    }
+
+    public boolean isTracker(String hostname) {
+        return hostnameToId.containsKey(hostname);
+    }
+
+    public String getTrackerId(String hostname) {
+        return hostnameToId.get(hostname);
+    }
+
+    public Tracker getTracker(String id) {
+        return trackersById.get(id);
+    }
+}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.java b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.java
new file mode 100644
index 0000000..9bfca7f
--- /dev/null
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.java
@@ -0,0 +1,153 @@
+/*
+ Copyright (C) 2022 ECORP
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ */
+
+package foundation.e.privacymodules.trackers.data;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import foundation.e.privacymodules.trackers.Tracker;
+
+public class WhitelistRepository {
+    private static final String SHARED_PREFS_FILE = "trackers_whitelist.prefs";
+    private static final String KEY_BLOKING_ENABLED = "blocking_enabled";
+    private static final String KEY_APPS_WHITELIST = "apps_whitelist";
+    private static final String KEY_APP_TRACKERS_WHITELIST_PREFIX = "app_trackers_whitelist_";
+    private static WhitelistRepository instance;
+
+    private boolean isBlockingEnabled = false;
+    private Set<Integer> appsWhitelist;
+    private Map<Integer, Set<String>> trackersWhitelistByApp = new HashMap();
+
+    private SharedPreferences prefs;
+    private WhitelistRepository(Context context) {
+        prefs = context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+        reloadCache();
+    }
+
+    public static WhitelistRepository getInstance(Context context) {
+        if (instance == null) {
+            instance = new WhitelistRepository(context);
+        }
+        return instance;
+    }
+
+    private void reloadCache() {
+        isBlockingEnabled = prefs.getBoolean(KEY_BLOKING_ENABLED, false);
+        reloadAppsWhiteList();
+        reloadAllAppTrackersWhiteList();
+    }
+
+    private void reloadAppsWhiteList() {
+        HashSet<Integer> appWhiteList = new HashSet();
+        for (String appUid: prefs.getStringSet(KEY_APPS_WHITELIST, new HashSet<String>())) {
+            try {
+                appWhiteList.add(Integer.parseInt(appUid));
+            } catch (Exception e) { }
+        }
+        this.appsWhitelist = appWhiteList;
+    }
+
+    private void reloadAppTrackersWhiteList(int appUid) {
+        String key = buildAppTrackersKey(appUid);
+        trackersWhitelistByApp.put(appUid, prefs.getStringSet(key, new HashSet<String>()));
+    }
+
+    private void reloadAllAppTrackersWhiteList() {
+        trackersWhitelistByApp.clear();
+        for (String key: prefs.getAll().keySet()) {
+            if (key.startsWith(KEY_APP_TRACKERS_WHITELIST_PREFIX)) {
+                int appUid = Integer.parseInt(key.substring(KEY_APP_TRACKERS_WHITELIST_PREFIX.length()));
+                reloadAppTrackersWhiteList(appUid);
+            }
+        }
+    }
+
+    public boolean isBlockingEnabled() { return isBlockingEnabled; }
+
+    public void setBlockingEnabled(boolean enabled) {
+        prefs.edit().putBoolean(KEY_BLOKING_ENABLED, enabled).apply();
+        isBlockingEnabled = enabled;
+    }
+
+    public void setWhiteListed(int appUid, boolean isWhiteListed) {
+        Set<String> current = new HashSet(prefs.getStringSet(KEY_APPS_WHITELIST, new HashSet<String>()));
+        if (isWhiteListed) {
+            current.add("" + appUid);
+        } else {
+            current.remove("" + appUid);
+        }
+
+        prefs.edit().putStringSet(KEY_APPS_WHITELIST, current).commit();
+        reloadAppsWhiteList();
+    }
+
+    private String buildAppTrackersKey(int appUid) {
+        return KEY_APP_TRACKERS_WHITELIST_PREFIX + appUid;
+    }
+
+    public void setWhiteListed(Tracker tracker, int appUid, boolean isWhiteListed) {
+        Set<String> trackers;
+        if (trackersWhitelistByApp.containsKey(appUid)) {
+            trackers = trackersWhitelistByApp.get(appUid);
+        } else {
+            trackers = new HashSet<String>();
+            trackersWhitelistByApp.put(appUid, trackers);
+        }
+        if (isWhiteListed) {
+            trackers.add(tracker.getId());
+        } else {
+            trackers.remove(tracker.getId());
+        }
+
+        prefs.edit().putStringSet(buildAppTrackersKey(appUid), trackers).commit();
+    }
+
+    public boolean isAppWhiteListed(int appUid) {
+        return appsWhitelist.contains(appUid);
+    }
+
+    public boolean isTrackerWhiteListedForApp(String trackerId, int appUid) {
+        return trackersWhitelistByApp.getOrDefault(appUid, new HashSet()).contains(trackerId);
+    }
+
+    public boolean areWhiteListEmpty() {
+        boolean empty = true;
+        for (Set<String> trackers: trackersWhitelistByApp.values()) {
+            empty = trackers.isEmpty();
+        }
+
+        return appsWhitelist.isEmpty() && empty;
+    }
+
+    public List<Integer> getWhiteListedApp() {
+        return new ArrayList(appsWhitelist);
+    }
+
+    public List<String> getWhiteListForApp(int appUid) {
+        return new ArrayList(trackersWhitelistByApp.getOrDefault(appUid, new HashSet()));
+    }
+}
-- 
cgit v1.2.1