Skip to content

Commit 517b16d

Browse files
committed
use up-to-date exodus signatures
1 parent fcce1cb commit 517b16d

7 files changed

Lines changed: 251 additions & 2277 deletions

File tree

app/src/main/assets/trackers.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

app/src/main/java/net/kollnig/missioncontrol/analysis/SignatureTrie.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,6 @@ class SignatureTrie {
4242
* @param id Tracker ID in database
4343
*/
4444
void insert(String signature, String name, String web, int id) {
45-
// Skip "good" trackers (marked with µ?)
46-
if (name.startsWith("µ?")) {
47-
return;
48-
}
49-
5045
TrieNode current = root;
5146
for (char c : signature.toCharArray()) {
5247
current = current.children.computeIfAbsent(c, k -> new TrieNode());

app/src/main/java/net/kollnig/missioncontrol/analysis/TrackerAnalysisManager.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
* via WorkManager.
3636
*/
3737
public class TrackerAnalysisManager {
38-
private static final int EXODUS_DATABASE_VERSION = 423;
3938
private static final String PREFS_NAME = "library_analysis";
4039
// Single work name ensures only one analysis runs at a time (prevents OOM)
4140
private static final String WORK_NAME = "tracker_analysis";
@@ -48,12 +47,16 @@ private TrackerAnalysisManager(Context context) {
4847
this.mContext = context.getApplicationContext();
4948
this.workManager = WorkManager.getInstance(mContext);
5049

51-
// Initialize/update database version - clears cache if Exodus DB updated
52-
SharedPreferences prefs = getPrefs();
53-
int current = prefs.getInt("version", Integer.MIN_VALUE);
54-
if (current < EXODUS_DATABASE_VERSION) {
55-
prefs.edit().clear().putInt("version", EXODUS_DATABASE_VERSION).apply();
56-
}
50+
// Trigger signature update in background
51+
new Thread(() -> {
52+
SharedPreferences prefs = getPrefs();
53+
long lastUpdate = prefs.getLong("last_signature_update", 0);
54+
if (System.currentTimeMillis() - lastUpdate >= 24 * 60 * 60 * 1000) {
55+
new TrackerSignatureManager(mContext).updateSignatures();
56+
long now = System.currentTimeMillis();
57+
prefs.edit().putLong("last_signature_update", now).apply();
58+
}
59+
}).start();
5760
}
5861

5962
public static synchronized TrackerAnalysisManager getInstance(Context context) {

app/src/main/java/net/kollnig/missioncontrol/analysis/TrackerLibraryAnalyser.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import androidx.annotation.NonNull;
2626

2727
import net.kollnig.missioncontrol.R;
28+
import net.kollnig.missioncontrol.data.ExodusTracker;
2829
import net.kollnig.missioncontrol.data.TrackerLibrary;
2930

3031
import org.jf.dexlib2.DexFileFactory;
@@ -83,13 +84,24 @@ public void setProgressCallback(AnalysisProgressCallback callback) {
8384
*/
8485
private synchronized SignatureTrie getSignatureTrie() {
8586
if (mSignatureTrie == null) {
86-
String[] signatures = mContext.getResources().getStringArray(R.array.trackers);
87-
String[] names = mContext.getResources().getStringArray(R.array.tname);
88-
String[] webs = mContext.getResources().getStringArray(R.array.tweb);
89-
9087
mSignatureTrie = new SignatureTrie();
91-
for (int i = 0; i < signatures.length; i++) {
92-
mSignatureTrie.insert(signatures[i], names[i], webs[i], i);
88+
TrackerSignatureManager manager = new TrackerSignatureManager(mContext);
89+
List<ExodusTracker> trackers = manager.getTrackers();
90+
91+
if (trackers != null) {
92+
for (ExodusTracker tracker : trackers) {
93+
if (tracker.codeSignature == null)
94+
continue;
95+
96+
// Exodus signatures are regex-like.
97+
// 1. Split alternatives "com.foo|com.bar"
98+
// 2. Unescape dots "com\.foo" -> "com.foo"
99+
String[] variations = tracker.codeSignature.split("\\|");
100+
for (String variation : variations) {
101+
String cleanSignature = variation.replace("\\.", ".");
102+
mSignatureTrie.insert(cleanSignature, tracker.name, tracker.website, tracker.id);
103+
}
104+
}
93105
}
94106
}
95107
return mSignatureTrie;
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package net.kollnig.missioncontrol.analysis;
2+
3+
import android.content.Context;
4+
import android.util.Log;
5+
6+
import net.kollnig.missioncontrol.data.ExodusTracker;
7+
8+
import org.json.JSONObject;
9+
10+
import java.io.BufferedReader;
11+
import java.io.File;
12+
import java.io.FileReader;
13+
import java.io.FileWriter;
14+
import java.io.IOException;
15+
import java.io.Reader;
16+
import java.util.ArrayList;
17+
import java.util.Collections;
18+
import java.util.Iterator;
19+
import java.util.List;
20+
21+
import okhttp3.OkHttpClient;
22+
import okhttp3.Request;
23+
import okhttp3.Response;
24+
25+
/**
26+
* Manages the fetch and cache of tracker signatures from Exodus Privacy.
27+
*/
28+
public class TrackerSignatureManager {
29+
private static final String TAG = "TrackerSignatureManager";
30+
private static final String EXODUS_URL = "https://reports.exodus-privacy.eu.org/api/trackers";
31+
private static final String CACHE_FILE = "trackers.json";
32+
33+
private final Context context;
34+
private final OkHttpClient client;
35+
36+
public TrackerSignatureManager(Context context) {
37+
this.context = context.getApplicationContext();
38+
this.client = new OkHttpClient();
39+
}
40+
41+
/**
42+
* Downloads the latest signatures from Exodus API and caches them.
43+
* Use this method in a background thread.
44+
*
45+
* @return true if signatures were updated (changed), false otherwise.
46+
*/
47+
public boolean updateSignatures() {
48+
Request request = new Request.Builder()
49+
.url(EXODUS_URL)
50+
.build();
51+
52+
try (Response response = client.newCall(request).execute()) {
53+
if (!response.isSuccessful()) {
54+
Log.e(TAG, "Failed to download signatures: " + response.code());
55+
return false;
56+
}
57+
58+
String json = response.body().string();
59+
60+
// Validate JSON before saving
61+
JSONObject root = new JSONObject(json);
62+
if (root.has("trackers")) {
63+
// Check if content changed
64+
File file = new File(context.getFilesDir(), CACHE_FILE);
65+
if (file.exists()) {
66+
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
67+
String cachedJson = readAll(reader);
68+
if (json.equals(cachedJson)) {
69+
Log.i(TAG, "Signatures are up to date.");
70+
return false;
71+
}
72+
} catch (Exception e) {
73+
Log.w(TAG, "Could not read existing cache for comparison: " + e.getMessage());
74+
}
75+
}
76+
77+
saveToCache(json);
78+
Log.i(TAG, "Successfully updated signatures.");
79+
return true;
80+
}
81+
} catch (Exception e) {
82+
Log.e(TAG, "Error updating signatures", e);
83+
}
84+
return false;
85+
}
86+
87+
private void saveToCache(String json) {
88+
File file = new File(context.getFilesDir(), CACHE_FILE);
89+
try (FileWriter writer = new FileWriter(file)) {
90+
writer.write(json);
91+
} catch (IOException e) {
92+
Log.e(TAG, "Failed to cache signatures", e);
93+
}
94+
}
95+
96+
/**
97+
* Gets the list of trackers.
98+
* Tries to read from cache first. If cache is missing or invalid, falls back to
99+
* bundled resources.
100+
*/
101+
public List<ExodusTracker> getTrackers() {
102+
List<ExodusTracker> cached = loadFromCache();
103+
if (cached != null && !cached.isEmpty()) {
104+
return cached;
105+
}
106+
return loadFromAssets();
107+
}
108+
109+
private List<ExodusTracker> loadFromCache() {
110+
File file = new File(context.getFilesDir(), CACHE_FILE);
111+
if (!file.exists()) {
112+
return null;
113+
}
114+
115+
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
116+
return parseTrackers(readAll(reader));
117+
} catch (Exception e) {
118+
Log.e(TAG, "Failed to load signatures from cache", e);
119+
}
120+
return null;
121+
}
122+
123+
private List<ExodusTracker> loadFromAssets() {
124+
Log.i(TAG, "Loading signatures from assets (fallback)");
125+
try (BufferedReader reader = new BufferedReader(
126+
new java.io.InputStreamReader(context.getAssets().open("trackers.json")))) {
127+
return parseTrackers(readAll(reader));
128+
} catch (IOException e) {
129+
Log.e(TAG, "Failed to load signatures from assets", e);
130+
}
131+
return Collections.emptyList();
132+
}
133+
134+
private List<ExodusTracker> parseTrackers(String jsonText) {
135+
try {
136+
JSONObject root = new JSONObject(jsonText);
137+
if (root.has("trackers")) {
138+
JSONObject trackersNode = root.getJSONObject("trackers");
139+
List<ExodusTracker> list = new ArrayList<>();
140+
Iterator<String> keys = trackersNode.keys();
141+
142+
while (keys.hasNext()) {
143+
String key = keys.next();
144+
JSONObject obj = trackersNode.getJSONObject(key);
145+
146+
ExodusTracker t = new ExodusTracker();
147+
t.id = obj.optInt("id");
148+
t.name = obj.optString("name");
149+
t.website = obj.optString("website");
150+
t.codeSignature = obj.optString("code_signature").isEmpty() ? null
151+
: obj.getString("code_signature");
152+
t.networkSignature = obj.optString("network_signature");
153+
154+
// Filter out "good" trackers
155+
if (t.name != null && (t.name.equals("Acrarium") || t.name.equals("ACRA")
156+
|| t.name.equals("Custom Activity On Crash"))) {
157+
continue;
158+
}
159+
160+
list.add(t);
161+
}
162+
return list;
163+
}
164+
} catch (Exception e) {
165+
Log.e(TAG, "Error parsing trackers JSON", e);
166+
}
167+
return null;
168+
}
169+
170+
private String readAll(Reader rd) throws IOException {
171+
StringBuilder sb = new StringBuilder();
172+
int cp;
173+
while ((cp = rd.read()) != -1) {
174+
sb.append((char) cp);
175+
}
176+
return sb.toString();
177+
}
178+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package net.kollnig.missioncontrol.data;
2+
3+
import java.util.List;
4+
5+
/**
6+
* Represents a tracker as defined in the Exodus Privacy database.
7+
*/
8+
public class ExodusTracker {
9+
public int id;
10+
11+
public String name;
12+
13+
public String website;
14+
15+
public String codeSignature;
16+
17+
public String networkSignature;
18+
19+
public List<String> categories;
20+
21+
public int getId() {
22+
return id;
23+
}
24+
25+
public String getName() {
26+
return name;
27+
}
28+
29+
public String getWebsite() {
30+
return website;
31+
}
32+
33+
public String getCodeSignature() {
34+
return codeSignature;
35+
}
36+
37+
public String getNetworkSignature() {
38+
return networkSignature;
39+
}
40+
41+
public List<String> getCategories() {
42+
return categories;
43+
}
44+
}

0 commit comments

Comments
 (0)