diff options
Diffstat (limited to 'trackers/src')
11 files changed, 1522 insertions, 0 deletions
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())); + } +} |