Skip to content

Commit 360c506

Browse files
committed
Restore install tracker analysis notifications
1 parent fe6995a commit 360c506

4 files changed

Lines changed: 150 additions & 18 deletions

File tree

app/src/main/java/eu/faircode/netguard/ServiceSinkhole.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@
9191
import net.kollnig.missioncontrol.data.TrackerBlocklist;
9292
import net.kollnig.missioncontrol.data.TrackerList;
9393

94-
import org.apache.commons.lang3.StringUtils;
9594
import org.json.JSONArray;
9695
import org.json.JSONException;
9796
import org.json.JSONObject;
@@ -2683,7 +2682,7 @@ public void notifyNewApplication(int uid, BroadcastReceiver br) {
26832682

26842683
// Check tracker libraries in app
26852684
if (br != null)
2686-
checkTrackers(packageName, uid, br, builder);
2685+
checkTrackers(packageName, uid, name, br, builder);
26872686
}
26882687

26892688
} catch (PackageManager.NameNotFoundException ex) {
@@ -2694,7 +2693,8 @@ public void notifyNewApplication(int uid, BroadcastReceiver br) {
26942693
}
26952694
}
26962695

2697-
private void checkTrackers(String packageName, int uid, BroadcastReceiver br, NotificationCompat.Builder builder) {
2696+
private void checkTrackers(String packageName, int uid, String appName, BroadcastReceiver br,
2697+
NotificationCompat.Builder builder) {
26982698
BroadcastReceiver.PendingResult result = br.goAsync();
26992699
new Thread() {
27002700
public void run() {
@@ -2706,13 +2706,12 @@ public void run() {
27062706
String cachedResult = manager.getCachedResult(packageName);
27072707
if (cachedResult != null && !manager.isCacheStale(packageName)) {
27082708
// Use cached result
2709-
int trackerCount = StringUtils.countMatches(cachedResult, "•");
2709+
int trackerCount = TrackerAnalysisManager.countTrackers(cachedResult);
27102710
builder.setContentText(getString(R.string.msg_installed_tracker_libraries_found, trackerCount));
27112711
NotificationManagerCompat.from(c).notify(uid, builder.build());
27122712
} else {
2713-
// Schedule analysis for later - notification will show generic message
2714-
manager.startAnalysis(packageName);
2715-
// Don't update notification here, user can check app details for results
2713+
// Schedule analysis for later; the worker updates this notification when done.
2714+
manager.startAnalysis(packageName, uid, appName);
27162715
}
27172716
} catch (Exception e) {
27182717
e.printStackTrace();

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

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@
3636
*/
3737
public class TrackerAnalysisManager {
3838
private static final String PREFS_NAME = "library_analysis";
39-
// Single work name ensures only one analysis runs at a time (prevents OOM)
40-
private static final String WORK_NAME = "tracker_analysis";
39+
private static final String WORK_NAME_PREFIX = "tracker_analysis_";
4140

4241
private static TrackerAnalysisManager instance;
4342
private final Context mContext;
@@ -68,34 +67,44 @@ public static synchronized TrackerAnalysisManager getInstance(Context context) {
6867

6968
/**
7069
* Starts an analysis for the given package using WorkManager.
71-
* Only one analysis runs at a time to prevent OOM; others are queued.
70+
* Duplicate requests for the same package are ignored while one is pending.
7271
* Observe progress via {@link #getWorkInfoByPackageLiveData(String)}.
7372
*
7473
* @param packageName The package to analyze
7574
*/
7675
public void startAnalysis(String packageName) {
77-
Data inputData = new Data.Builder()
76+
startAnalysis(packageName, -1, null);
77+
}
78+
79+
/**
80+
* Starts an analysis and optionally updates an install notification with the
81+
* result when the worker finishes.
82+
*/
83+
public void startAnalysis(String packageName, int notificationUid, @Nullable String appName) {
84+
Data.Builder dataBuilder = new Data.Builder()
7885
.putString(TrackerAnalysisWorker.KEY_PACKAGE_NAME, packageName)
79-
.build();
86+
.putInt(TrackerAnalysisWorker.KEY_NOTIFICATION_UID, notificationUid);
87+
if (appName != null)
88+
dataBuilder.putString(TrackerAnalysisWorker.KEY_APP_NAME, appName);
89+
90+
Data inputData = dataBuilder.build();
8091

8192
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(TrackerAnalysisWorker.class)
8293
.setInputData(inputData)
8394
.addTag(packageName)
8495
.build();
8596

86-
// Use global work name + APPEND to serialize all analyses (prevents OOM from
87-
// concurrent scans)
8897
workManager.enqueueUniqueWork(
89-
WORK_NAME,
90-
ExistingWorkPolicy.APPEND_OR_REPLACE,
98+
getWorkName(packageName),
99+
ExistingWorkPolicy.KEEP,
91100
workRequest);
92101
}
93102

94103
/**
95-
* Observe work status for a given package (by tag).
104+
* Observe work status for the package's unique analysis work.
96105
*/
97106
public LiveData<java.util.List<WorkInfo>> getWorkInfoByPackageLiveData(String packageName) {
98-
return workManager.getWorkInfosByTagLiveData(packageName);
107+
return workManager.getWorkInfosForUniqueWorkLiveData(getWorkName(packageName));
99108
}
100109

101110
@Nullable
@@ -124,6 +133,24 @@ public void cacheResult(String packageName, String result, int versionCode) {
124133
.apply();
125134
}
126135

136+
public static int countTrackers(String result) {
137+
if (result == null)
138+
return 0;
139+
140+
int count = 0;
141+
int index = result.indexOf("•");
142+
while (index >= 0) {
143+
count++;
144+
index = result.indexOf("•", index + 1);
145+
}
146+
147+
return count;
148+
}
149+
150+
static String getWorkName(String packageName) {
151+
return WORK_NAME_PREFIX + packageName;
152+
}
153+
127154
private SharedPreferences getPrefs() {
128155
return mContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
129156
}

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

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,43 @@
1717

1818
package net.kollnig.missioncontrol.analysis;
1919

20+
import static net.kollnig.missioncontrol.DetailsActivity.INTENT_EXTRA_APP_NAME;
21+
import static net.kollnig.missioncontrol.DetailsActivity.INTENT_EXTRA_APP_PACKAGENAME;
22+
import static net.kollnig.missioncontrol.DetailsActivity.INTENT_EXTRA_APP_UID;
23+
24+
import android.app.Notification;
25+
import android.app.PendingIntent;
2026
import android.content.Context;
27+
import android.content.Intent;
2128
import android.content.pm.PackageInfo;
2229
import android.content.pm.PackageManager;
30+
import android.net.Uri;
31+
import android.os.Build;
32+
import android.provider.Settings;
33+
import android.util.Log;
2334

2435
import androidx.annotation.NonNull;
36+
import androidx.core.app.NotificationCompat;
37+
import androidx.core.app.NotificationManagerCompat;
2538
import androidx.work.Data;
2639
import androidx.work.Worker;
2740
import androidx.work.WorkerParameters;
2841

42+
import eu.faircode.netguard.PendingIntentCompat;
43+
import eu.faircode.netguard.Util;
44+
import net.kollnig.missioncontrol.DetailsActivity;
45+
import net.kollnig.missioncontrol.R;
46+
2947
import java.util.concurrent.atomic.AtomicInteger;
48+
import java.util.concurrent.Semaphore;
3049

3150
public class TrackerAnalysisWorker extends Worker {
51+
private static final String TAG = TrackerAnalysisWorker.class.getSimpleName();
52+
private static final Semaphore ANALYSIS_SEMAPHORE = new Semaphore(1);
53+
3254
public static final String KEY_PACKAGE_NAME = "package_name";
55+
public static final String KEY_NOTIFICATION_UID = "notification_uid";
56+
public static final String KEY_APP_NAME = "app_name";
3357
public static final String KEY_RESULT = "result";
3458
public static final String KEY_ERROR = "error";
3559
public static final String KEY_PROGRESS = "progress";
@@ -48,17 +72,23 @@ public Result doWork() {
4872
.build());
4973
}
5074

75+
boolean acquired = false;
5176
try {
5277
Context context = getApplicationContext();
5378
PackageInfo pkg = context.getPackageManager().getPackageInfo(packageName, 0);
5479

80+
ANALYSIS_SEMAPHORE.acquire();
81+
acquired = true;
82+
5583
// Perform analysis with progress reporting
5684
String result = doAnalysis(context, packageName);
5785

5886
// Cache the result
5987
TrackerAnalysisManager.getInstance(context)
6088
.cacheResult(packageName, result, pkg.versionCode);
6189

90+
updateInstallNotification(context, packageName, result);
91+
6292
return Result.success(new Data.Builder()
6393
.putString(KEY_RESULT, result)
6494
.build());
@@ -75,6 +105,9 @@ public Result doWork() {
75105
return Result.failure(new Data.Builder()
76106
.putString(KEY_ERROR, e.getMessage() != null ? e.getMessage() : "Unknown error")
77107
.build());
108+
} finally {
109+
if (acquired)
110+
ANALYSIS_SEMAPHORE.release();
78111
}
79112
}
80113

@@ -91,4 +124,54 @@ private String doAnalysis(Context context, String packageName) throws AnalysisEx
91124
});
92125
return analyser.analyseApp(packageName);
93126
}
127+
128+
private void updateInstallNotification(Context context, String packageName, String result) {
129+
int uid = getInputData().getInt(KEY_NOTIFICATION_UID, -1);
130+
if (uid < 0 || !Util.canNotify(context))
131+
return;
132+
133+
String appName = getInputData().getString(KEY_APP_NAME);
134+
if (appName == null)
135+
appName = packageName;
136+
137+
try {
138+
NotificationManagerCompat.from(context).notify(uid,
139+
buildInstallNotification(context, packageName, uid, appName, result));
140+
} catch (SecurityException ex) {
141+
Log.w(TAG, "SecurityException updating install notification for uid " + uid + ": " + ex.getMessage());
142+
}
143+
}
144+
145+
static Notification buildInstallNotification(Context context, String packageName, int uid, String appName,
146+
String result) {
147+
int trackerCount = TrackerAnalysisManager.countTrackers(result);
148+
149+
Intent main = new Intent(context, DetailsActivity.class);
150+
main.putExtra(INTENT_EXTRA_APP_NAME, appName);
151+
main.putExtra(INTENT_EXTRA_APP_PACKAGENAME, packageName);
152+
main.putExtra(INTENT_EXTRA_APP_UID, uid);
153+
PendingIntent pi = PendingIntentCompat.getActivity(context, uid, main,
154+
PendingIntent.FLAG_UPDATE_CURRENT);
155+
156+
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "notify");
157+
builder.setSmallIcon(R.drawable.ic_rocket_white)
158+
.setContentIntent(pi)
159+
.addAction(0, context.getString(R.string.title_activity_detail), pi)
160+
.setColor(context.getResources().getColor(R.color.colorTrackerControl))
161+
.setAutoCancel(true);
162+
builder.setContentTitle(context.getString(R.string.msg_installed, appName))
163+
.setContentText(context.getString(R.string.msg_installed_tracker_libraries_found, trackerCount));
164+
165+
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
166+
intent.setData(Uri.parse("package:" + packageName));
167+
PendingIntent piUninstall = PendingIntentCompat.getActivity(context, uid + 10000, intent,
168+
PendingIntent.FLAG_UPDATE_CURRENT);
169+
builder.addAction(0, context.getString(R.string.uninstall), piUninstall);
170+
171+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
172+
builder.setCategory(NotificationCompat.CATEGORY_STATUS)
173+
.setVisibility(NotificationCompat.VISIBILITY_SECRET);
174+
175+
return builder.build();
176+
}
94177
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package net.kollnig.missioncontrol.analysis;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import org.junit.Test;
6+
7+
public class TrackerAnalysisManagerTest {
8+
@Test
9+
public void countTrackersCountsBulletPrefixedAnalysisRows() {
10+
assertEquals(0, TrackerAnalysisManager.countTrackers(null));
11+
assertEquals(0, TrackerAnalysisManager.countTrackers("None"));
12+
assertEquals(1, TrackerAnalysisManager.countTrackers("\n• Google"));
13+
assertEquals(3, TrackerAnalysisManager.countTrackers("\n• Google\n• Meta\n• Branch"));
14+
}
15+
16+
@Test
17+
public void workNameIsUniquePerPackage() {
18+
assertEquals("tracker_analysis_org.example.one",
19+
TrackerAnalysisManager.getWorkName("org.example.one"));
20+
assertEquals("tracker_analysis_org.example.two",
21+
TrackerAnalysisManager.getWorkName("org.example.two"));
22+
}
23+
}

0 commit comments

Comments
 (0)