Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 35 additions & 118 deletions app/src/main/java/eu/faircode/netguard/ServiceSinkhole.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,6 @@
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.zip.GZIPInputStream;

Expand Down Expand Up @@ -216,7 +214,7 @@ public enum Command {

private static volatile PowerManager.WakeLock wlInstance = null;

private ExecutorService executor = Executors.newCachedThreadPool();
// executor removed: was only used by old interactiveStateReceiver reload logic

private static final String ACTION_HOUSE_HOLDING = "eu.faircode.netguard.HOUSE_HOLDING";
private static final String ACTION_SCREEN_OFF_DELAYED = "eu.faircode.netguard.SCREEN_OFF_DELAYED";
Expand Down Expand Up @@ -559,23 +557,6 @@ private void start() {
private void reload(boolean interactive) {
List<Rule> listRule = Rule.getRules(true, ServiceSinkhole.this);

// Check if rules needs to be reloaded
if (interactive) {
boolean process = false;
for (Rule rule : listRule) {
boolean blocked = (last_metered ? rule.other_blocked : rule.wifi_blocked);
boolean screen = (last_metered ? rule.screen_other : rule.screen_wifi);
if (blocked && screen) {
process = true;
break;
}
}
if (!process) {
Log.i(TAG, "No changed rules on interactive state change");
return;
}
}

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ServiceSinkhole.this);

// Refresh cached preferences for shouldTrackApp()
Expand Down Expand Up @@ -1965,69 +1946,25 @@ private void prepareForwarding() {
}

private List<Rule> getAllowedRules(List<Rule> listRule) {
List<Rule> listAllowed = new ArrayList<>();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

// Check state
boolean wifi = Util.isWifiActive(this);
boolean metered = Util.isMeteredNetwork(this);
boolean useMetered = prefs.getBoolean("use_metered", false);
String ssidNetwork = Util.getWifiSSID(this);
String generation = Util.getNetworkGeneration(this);
boolean unmetered_2g = prefs.getBoolean("unmetered_2g", false);
boolean unmetered_3g = prefs.getBoolean("unmetered_3g", false);
boolean unmetered_4g = prefs.getBoolean("unmetered_4g", false);
boolean roaming = Util.isRoaming(ServiceSinkhole.this);
boolean national = prefs.getBoolean("national_roaming", false);
boolean eu = prefs.getBoolean("eu_roaming", false);
boolean tethering = prefs.getBoolean("tethering", false);
boolean filter = prefs.getBoolean("filter", true);

// Update connected state
// TrackerControl: In filter mode (always on for TC), the allowed rules list
// doesn't affect VPN routing or tracker blocking. The VPN builder uses rule.apply
// (network-independent) and tracker blocking is handled dynamically in
// blockKnownTracker(). We just return all rules for the notification count.
last_connected = Util.isConnected(ServiceSinkhole.this);

boolean org_metered = metered;
boolean org_roaming = roaming;

// Update metered state
if (wifi && !useMetered)
metered = false;
if (unmetered_2g && "2G".equals(generation))
metered = false;
if (unmetered_3g && "3G".equals(generation))
metered = false;
if (unmetered_4g && "4G".equals(generation))
// Preserve metered override: treat WiFi as unmetered unless use_metered is set.
// This affects isLockedDown() which is still used for IP filter rules.
boolean metered = Util.isMeteredNetwork(this);
if (Util.isWifiActive(this) &&
!PreferenceManager.getDefaultSharedPreferences(this).getBoolean("use_metered", false))
metered = false;
last_metered = metered;

// Update roaming state
if (roaming && eu)
roaming = !Util.isEU(this);
if (roaming && national)
roaming = !Util.isNational(this);

Log.i(TAG, "Get allowed" +
" connected=" + last_connected +
" wifi=" + wifi +
" network=" + ssidNetwork +
" metered=" + metered + "/" + org_metered +
" generation=" + generation +
" roaming=" + roaming + "/" + org_roaming +
" interactive=" + last_interactive +
" tethering=" + tethering +
" filter=" + filter);
Log.i(TAG, "Get allowed connected=" + last_connected +
" metered=" + last_metered +
" rules=" + listRule.size());

if (last_connected)
for (Rule rule : listRule) {
boolean blocked = (metered ? rule.other_blocked : rule.wifi_blocked);
boolean screen = (metered ? rule.screen_other : rule.screen_wifi);
if ((!blocked || (screen && last_interactive)) &&
(!metered || !(rule.roaming && roaming)))
listAllowed.add(rule);
}

Log.i(TAG, "Allowed " + listAllowed.size() + " of " + listRule.size());
return listAllowed;
return new ArrayList<>(listRule);
}

private void stopVPN(ParcelFileDescriptor pfd) {
Expand Down Expand Up @@ -2378,33 +2315,14 @@ public void onReceive(final Context context, final Intent intent) {
Log.i(TAG, "Received " + intent);
Util.logExtras(intent);

executor.submit(new Runnable() {
@Override
public void run() {
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(ACTION_SCREEN_OFF_DELAYED);
i.setPackage(context.getPackageName());
PendingIntent pi = PendingIntentCompat.getBroadcast(context, 0, i,
PendingIntent.FLAG_UPDATE_CURRENT);
am.cancel(pi);

try {
last_interactive = Intent.ACTION_SCREEN_ON.equals(intent.getAction());
reload("interactive state changed", ServiceSinkhole.this, true);

// Start/stop stats
statsHandler.sendEmptyMessage(
Util.isInteractive(ServiceSinkhole.this) ? MSG_STATS_START : MSG_STATS_STOP);
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
// TrackerControl: skip VPN rebuild on screen on/off.
// TC doesn't use screen-based per-network blocking rules (screen_wifi/screen_other).
// Just update the interactive state for stats handling.
last_interactive = Intent.ACTION_SCREEN_ON.equals(intent.getAction());

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
am.set(AlarmManager.RTC_WAKEUP, new Date().getTime() + 15 * 1000L, pi);
else
am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, new Date().getTime() + 15 * 1000L, pi);
}
}
});
// Start/stop stats
statsHandler.sendEmptyMessage(
Util.isInteractive(ServiceSinkhole.this) ? MSG_STATS_START : MSG_STATS_STOP);
}
};

Expand Down Expand Up @@ -2445,6 +2363,8 @@ public void onReceive(Context context, Intent intent) {
Log.i(TAG, "device idle=" + pm.isDeviceIdleMode());

// Reload rules when coming from idle mode
// This ensures the native tunnel thread is healthy after Doze
// network restrictions are lifted
if (!pm.isDeviceIdleMode())
reload("idle state changed", ServiceSinkhole.this, false);
}
Expand All @@ -2454,9 +2374,10 @@ public void onReceive(Context context, Intent intent) {
@Override
@TargetApi(Build.VERSION_CODES.M)
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received " + intent);
// TrackerControl: skip VPN rebuild on hotspot state changes.
// TC's tracker blocking is independent of tethering/AP state.
Log.i(TAG, "AP state changed (skipping reload): " + intent);
Util.logExtras(intent);
reload("AP state changed", ServiceSinkhole.this, false);
}
};

Expand All @@ -2471,10 +2392,12 @@ public void onReceive(Context context, Intent intent) {
return;
}

// Reload rules
Log.i(TAG, "Received " + intent);
// TrackerControl: skip VPN rebuild on network changes.
// Unlike NetGuard, TC doesn't apply different blocking rules per network type
// (WiFi vs mobile). Tracker blocking is handled dynamically in blockKnownTracker()
// and the VPN app routing (filter mode) is network-independent.
Log.i(TAG, "Connectivity changed (skipping reload): " + intent);
Util.logExtras(intent);
reload("connectivity changed", ServiceSinkhole.this, false);
}
};

Expand Down Expand Up @@ -2581,17 +2504,11 @@ private void checkConnectivity(Network network, NetworkInfo ni, NetworkCapabilit
public void onDataConnectionStateChanged(int state, int networkType) {
if (state == TelephonyManager.DATA_CONNECTED) {
String current_generation = Util.getNetworkGeneration(ServiceSinkhole.this);
Log.i(TAG, "Data connected generation=" + current_generation);

// TrackerControl: skip VPN rebuild on network generation changes.
// TC doesn't differentiate blocking by 2G/3G/4G network type.
if (last_generation == null || !last_generation.equals(current_generation)) {
Log.i(TAG, "New network generation=" + current_generation);
Log.i(TAG, "Network generation changed to " + current_generation + " (skipping reload)");
last_generation = current_generation;

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ServiceSinkhole.this);
if (prefs.getBoolean("unmetered_2g", false) ||
prefs.getBoolean("unmetered_3g", false) ||
prefs.getBoolean("unmetered_4g", false))
reload("data connection state changed", ServiceSinkhole.this, false);
}
}
}
Expand Down Expand Up @@ -3239,7 +3156,7 @@ public void onDestroy() {
}
}

executor.shutdownNow();
// executor.shutdownNow() removed: executor no longer used

super.onDestroy();
}
Expand Down
Loading