From c8d88ec3364218802bc48257b7766ad8f19a6e45 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Wed, 17 Aug 2022 08:49:03 +0000 Subject: 2-Simplify sources modules tree --- 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 +++++++ 11 files changed, 1522 insertions(+) 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/src') 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 @@ + + + + + + + + + + + + + + + + + + \ 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 queue; + private boolean stopped = false; + + + public TrackersLogger(Context context) { + statsRepository = StatsRepository.getInstance(context); + queue = new LinkedBlockingQueue(); + 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 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 getWhiteList(int appUid) { + List trackers = new ArrayList(); + for (String trackerId: whitelistRepository.getWhiteListForApp(appUid)) { + trackers.add(trackersRepository.getTracker(trackerId)); + } + return trackers; + } + + @Override + public List 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 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 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> getPastDayTrackersCalls() { + return statsRepository.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS); + } + + @Override + public List> getPastMonthTrackersCalls() { + return statsRepository.getTrackersCallsOnPeriod(30, ChronoUnit.DAYS); + } + + @Override + public List> getPastYearTrackersCalls() { + return statsRepository.getTrackersCallsOnPeriod(12, ChronoUnit.MONTHS); + } + + @Override + public int getTrackersCount() { + return statsRepository.getContactedTrackersCount(); + } + + @Override + public Map getTrackersCountByApp() { + return statsRepository.getContactedTrackersCountByApp(); + } + + @Override + public List 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> getPastDayTrackersCallsByApps() { + return statsRepository.getCallsByApps(24, ChronoUnit.HOURS); + } + + @NotNull + @Override + public Pair 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> 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> 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> callsByPeriodToPeriodsList( + Map> callsByPeriod, + int periodsCount, + TemporalUnit periodUnit, + String javaPeriodFormat + ) { + ZonedDateTime currentDate = ZonedDateTime.now().minus(periodsCount, periodUnit); + DateTimeFormatter formater = DateTimeFormatter.ofPattern(javaPeriodFormat); + + List> 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> 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> 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 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 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> 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> 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 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> callsByApp = new HashMap<>(); + + Pair 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 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 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 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 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 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> getTrackersCallsOnPeriod(int periodsCount, TemporalUnit periodUnit) { + return database.getTrackersCallsOnPeriod(periodsCount, periodUnit); + } + + public int getActiveTrackersByPeriod(int periodsCount, TemporalUnit periodUnit) { + return database.getActiveTrackersByPeriod(periodsCount, periodUnit); + } + + public Map getContactedTrackersCountByApp() { + return database.getContactedTrackersCountByApp(); + } + + public int getContactedTrackersCount() { + return database.getContactedTrackersCount(); + } + + public List getAllTrackersOfApp(int app_uid) { + return database.getAllTrackersOfApp(app_uid); + } + + public Map> getCallsByApps(int periodCount, TemporalUnit periodUnit) { + return database.getCallsByApps(periodCount, periodUnit); + } + + public Pair 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 trackersById = new HashMap(); + private Map hostnameToId = new HashMap(); + + public void setTrackersList(List list) { + Map trackersById = new HashMap(); + Map 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 appsWhitelist; + private Map> 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 appWhiteList = new HashSet(); + for (String appUid: prefs.getStringSet(KEY_APPS_WHITELIST, new HashSet())) { + 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())); + } + + 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 current = new HashSet(prefs.getStringSet(KEY_APPS_WHITELIST, new HashSet())); + 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 trackers; + if (trackersWhitelistByApp.containsKey(appUid)) { + trackers = trackersWhitelistByApp.get(appUid); + } else { + trackers = new HashSet(); + 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 trackers: trackersWhitelistByApp.values()) { + empty = trackers.isEmpty(); + } + + return appsWhitelist.isEmpty() && empty; + } + + public List getWhiteListedApp() { + return new ArrayList(appsWhitelist); + } + + public List getWhiteListForApp(int appUid) { + return new ArrayList(trackersWhitelistByApp.getOrDefault(appUid, new HashSet())); + } +} -- cgit v1.2.1