Disable the target apps without clearing the selection

The target apps toggle is now independent from the actual
apps filter, so that the user can disable it without clearing
the apps selection and enable it afterwards.

Closes #855
This commit is contained in:
emanuele-f 2026-05-13 19:26:53 +02:00
parent 25b834cd0c
commit 6c0cfd811d
4 changed files with 75 additions and 27 deletions

View File

@ -195,7 +195,7 @@ public class PCAPdroid extends Application {
private void removeUninstalledAppsFromAppFilter() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
Set<String> filter = Prefs.getAppFilter(prefs);
Set<String> filter = Prefs.getAppFilterRaw(prefs);
ArrayList<String> to_remove = new ArrayList<>();
PackageManager pm = getPackageManager();

View File

@ -90,6 +90,7 @@ public class StatusFragment extends Fragment implements AppStateListener, MenuPr
private TextView mFilterDescription;
private SwitchCompat mAppFilterSwitch;
private Set<String> mAppFilter;
private boolean mAppFilterEnabled;
private TextView mFilterRootDecryptionWarning;
private View mLastCaptureSection;
private View mLastCapture;
@ -141,7 +142,8 @@ public class StatusFragment extends Fragment implements AppStateListener, MenuPr
mQuickSettings = view.findViewById(R.id.quick_settings);
mFilterRootDecryptionWarning = view.findViewById(R.id.app_filter_root_decryption_warning);
mPrefs = PreferenceManager.getDefaultSharedPreferences(mActivity);
mAppFilter = Prefs.getAppFilter(mPrefs);
mAppFilter = Prefs.getAppFilterRaw(mPrefs);
mAppFilterEnabled = Prefs.isAppFilterEnabled(mPrefs);
PrefSpinner.init(view.findViewById(R.id.dump_mode_spinner),
R.array.pcap_dump_modes, R.array.pcap_dump_modes_labels, R.array.pcap_dump_modes_descriptions,
@ -162,10 +164,9 @@ public class StatusFragment extends Fragment implements AppStateListener, MenuPr
filterTitle.setText(R.string.target_apps);
mAppFilterSwitch.setOnClickListener((buttonView) -> {
mAppFilterSwitch.setChecked(!mAppFilterSwitch.isChecked());
openAppFilterSelector();
});
filterRow.setOnClickListener((v) -> openAppFilterSelector());
mAppFilterSwitch.setOnClickListener((buttonView) -> onAppFilterSwitchClicked());
refreshFilterInfo();
@ -204,11 +205,11 @@ public class StatusFragment extends Fragment implements AppStateListener, MenuPr
}
private void recheckFilterWarning() {
boolean hasFilter = ((mAppFilter != null) && (!mAppFilter.isEmpty()));
boolean hasEffectiveFilter = mAppFilterEnabled && (mAppFilter != null) && (!mAppFilter.isEmpty());
mFilterRootDecryptionWarning.setVisibility((Prefs.getTlsDecryptionEnabled(mPrefs) &&
Prefs.isRootCaptureEnabled(mPrefs)
&& !hasFilter) ? View.VISIBLE : View.GONE);
&& !hasEffectiveFilter) ? View.VISIBLE : View.GONE);
}
private void refreshDecryptionStatus() {
@ -226,15 +227,17 @@ public class StatusFragment extends Fragment implements AppStateListener, MenuPr
if(context == null)
return;
if((mAppFilter == null) || (mAppFilter.isEmpty())) {
boolean hasApps = (mAppFilter != null) && (!mAppFilter.isEmpty());
boolean effective = hasApps && mAppFilterEnabled;
mAppFilterSwitch.setChecked(effective);
if (!effective) {
mFilterDescription.setText(R.string.capture_all_apps);
mFilterIcon.setVisibility(View.GONE);
mAppFilterSwitch.setChecked(false);
return;
}
mAppFilterSwitch.setChecked(true);
Pair<String, Drawable> pair = getAppFilterTextAndIcon(context);
mFilterDescription.setText(pair.first);
@ -245,6 +248,23 @@ public class StatusFragment extends Fragment implements AppStateListener, MenuPr
}
}
private void onAppFilterSwitchClicked() {
boolean hasApps = (mAppFilter != null) && (!mAppFilter.isEmpty());
if (!hasApps) {
mAppFilterSwitch.setChecked(false);
mPrefs.edit().putBoolean(Prefs.PREF_APP_FILTER_ENABLED, true).apply();
mAppFilterEnabled = true;
openAppFilterSelector();
return;
}
mAppFilterEnabled = !mAppFilterEnabled;
mPrefs.edit().putBoolean(Prefs.PREF_APP_FILTER_ENABLED, mAppFilterEnabled).apply();
refreshFilterInfo();
recheckFilterWarning();
}
private void onStatsUpdate(CaptureStats stats) {
Log.d("MainReceiver", "Got StatsUpdate: bytes_sent=" + stats.pkts_sent + ", bytes_rcvd=" +
stats.bytes_rcvd + ", pkts_sent=" + stats.pkts_sent + ", pkts_rcvd=" + stats.pkts_rcvd);
@ -361,7 +381,8 @@ public class StatusFragment extends Fragment implements AppStateListener, MenuPr
mCollectorInfoLayout.setVisibility(View.GONE);
mInterfaceInfo.setVisibility(View.GONE);
mQuickSettings.setVisibility(View.VISIBLE);
mAppFilter = Prefs.getAppFilter(mPrefs);
mAppFilter = Prefs.getAppFilterRaw(mPrefs);
mAppFilterEnabled = Prefs.isAppFilterEnabled(mPrefs);
refreshFilterInfo();
refreshLastCapture();
break;
@ -401,6 +422,7 @@ public class StatusFragment extends Fragment implements AppStateListener, MenuPr
mInterfaceInfo.setVisibility(View.GONE);
mAppFilter = CaptureService.getAppFilter();
mAppFilterEnabled = (mAppFilter != null) && (!mAppFilter.isEmpty());
refreshPcapDumpInfo(context);
break;
default:

View File

@ -70,6 +70,7 @@ public class Prefs {
public static final String PREF_FIREWALL = "firewall";
public static final String PREF_TLS_DECRYPTION_KEY = "tls_decryption";
public static final String PREF_APP_FILTER = "app_filter";
public static final String PREF_APP_FILTER_ENABLED = "app_filter_enabled";
public static final String PREF_HTTP_SERVER_PORT = "http_server_port";
public static final String PREF_PCAP_DUMP_MODE = "pcap_dump_mode_v2";
public static final String PREF_IP_MODE = "ip_mode";
@ -207,7 +208,9 @@ public class Prefs {
public static boolean isSocks5AuthEnabled(SharedPreferences p) { return(p.getBoolean(PREF_SOCKS5_AUTH_ENABLED_KEY, false)); }
public static String getSocks5Username(SharedPreferences p) { return(p.getString(PREF_SOCKS5_USERNAME_KEY, "")); }
public static String getSocks5Password(SharedPreferences p) { return(p.getString(PREF_SOCKS5_PASSWORD_KEY, "")); }
public static Set<String> getAppFilter(SharedPreferences p) { return(getStringSet(p, PREF_APP_FILTER)); }
public static Set<String> getAppFilter(SharedPreferences p) { return(isAppFilterEnabled(p) ? getAppFilterRaw(p) : new ArraySet<>()); }
public static Set<String> getAppFilterRaw(SharedPreferences p) { return(getStringSet(p, PREF_APP_FILTER)); }
public static boolean isAppFilterEnabled(SharedPreferences p) { return(p.getBoolean(PREF_APP_FILTER_ENABLED, true)); }
public static IpMode getIPMode(SharedPreferences p) { return(getIPMode(p.getString(PREF_IP_MODE, IP_MODE_DEFAULT))); }
public static BlockQuicMode getBlockQuicMode(SharedPreferences p) { return(getBlockQuicMode(p.getString(PREF_BLOCK_QUIC, BLOCK_QUIC_MODE_DEFAULT))); }
public static String getAppLocale(SharedPreferences p) {

View File

@ -91,34 +91,57 @@
tools:listitem="@layout/quick_settings_item"
android:prompt="@string/traffic_dump"/>
<RelativeLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="wrap_content"
android:orientation="vertical">
<include
android:id="@+id/app_filter_text"
layout="@layout/quick_settings_item"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="50dp" />
android:orientation="horizontal"
android:gravity="center_vertical"
android:baselineAligned="false">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/app_filter_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<FrameLayout
android:id="@+id/app_filter_text"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
<include layout="@layout/quick_settings_item" />
</FrameLayout>
<View
android:layout_width="1dp"
android:layout_height="32dp"
android:layout_marginHorizontal="8dp"
android:background="?android:attr/listDivider" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/app_filter_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48dp"
android:minHeight="48dp"
android:gravity="center"
android:paddingHorizontal="8dp"
android:background="?attr/selectableItemBackgroundBorderless" />
</LinearLayout>
<TextView
android:id="@+id/app_filter_root_decryption_warning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/app_filter_text"
android:paddingHorizontal="10dp"
android:layout_marginTop="5dp"
android:text="@string/decryption_no_filter_warn"
android:textColor="@color/warning"
android:textSize="14sp" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout