Apps in VPN exceptions are now searchable

This commit is contained in:
emanuele-f 2022-07-05 17:25:01 +02:00
parent 86d29732d4
commit 83c9cff3a8
8 changed files with 98 additions and 19 deletions

View File

@ -47,7 +47,9 @@ public class AppsTogglesAdapter extends RecyclerView.Adapter<AppsTogglesAdapter.
private final LayoutInflater mLayoutInflater;
private final Set<String> mCheckedItems;
private AppToggleListener mListener;
private String mFilter = "";
private List<AppDescriptor> mApps = new ArrayList<>();
private final List<AppDescriptor> mFilteredApps = new ArrayList<>();
private @Nullable RecyclerView mRecyclerView;
public AppsTogglesAdapter(Context context, Set<String> checkedItems) {
@ -125,16 +127,23 @@ public class AppsTogglesAdapter extends RecyclerView.Adapter<AppsTogglesAdapter.
holder.icon.setImageDrawable(app.getIcon());
}
private List<AppDescriptor> getApps() {
if(mFilter.isEmpty())
return mApps;
else
return mFilteredApps;
}
@Override
public int getItemCount() {
return mApps.size();
return getApps().size();
}
public AppDescriptor getItem(int pos) {
if((pos < 0) || (pos > mApps.size()))
if((pos < 0) || (pos > getItemCount()))
return null;
return mApps.get(pos);
return getApps().get(pos);
}
private void handleToggle(int old_pos, boolean checked) {
@ -152,10 +161,12 @@ public class AppsTogglesAdapter extends RecyclerView.Adapter<AppsTogglesAdapter.
if(mListener != null)
mListener.onAppToggled(app, checked);
List<AppDescriptor> apps = getApps();
// determine the new item position
int new_pos = old_pos;
for(int i=0; i<mApps.size(); i++) {
AppDescriptor other = mApps.get(i);
for(int i=0; i<apps.size(); i++) {
AppDescriptor other = apps.get(i);
if((i != old_pos) && compareCheckedFirst(app, other) <= 0) {
new_pos = i;
@ -170,8 +181,8 @@ public class AppsTogglesAdapter extends RecyclerView.Adapter<AppsTogglesAdapter.
notifyItemChanged(old_pos);
if(new_pos != old_pos) {
mApps.remove(old_pos);
mApps.add(new_pos, app);
apps.remove(old_pos);
apps.add(new_pos, app);
notifyItemMoved(old_pos, new_pos);
if(mRecyclerView != null) {
@ -196,11 +207,28 @@ public class AppsTogglesAdapter extends RecyclerView.Adapter<AppsTogglesAdapter.
}
@SuppressLint("NotifyDataSetChanged")
private void refreshedFiteredApps() {
mFilteredApps.clear();
if(!mFilter.isEmpty()) {
for(AppDescriptor app: mApps) {
if(app.matches(mFilter, false))
mFilteredApps.add(app);
}
}
Collections.sort(getApps(), this::compareCheckedFirst);
notifyDataSetChanged();
}
public void setApps(List<AppDescriptor> apps) {
mApps = apps;
Collections.sort(mApps, this::compareCheckedFirst);
refreshedFiteredApps();
}
notifyDataSetChanged();
public void setFilter(String text) {
mFilter = text;
refreshedFiteredApps();
}
public void setAppToggleListener(final AppToggleListener listener) {

View File

@ -19,6 +19,7 @@
package com.emanuelef.remote_capture.adapters;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
@ -370,6 +371,7 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
fixFilteredPositions(first_removed_pos);
}
@SuppressLint("NotifyDataSetChanged")
public void refreshFilteredConnections() {
final ConnectionsRegister reg = CaptureService.getConnsRegister();
if(reg == null)

View File

@ -21,6 +21,8 @@ package com.emanuelef.remote_capture.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@ -28,6 +30,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.Fragment;
import com.emanuelef.remote_capture.AppsLoader;
@ -42,8 +45,11 @@ import java.util.Set;
import kotlin.NotImplementedError;
public abstract class AppsToggles extends Fragment implements AppsLoadListener, AppsTogglesAdapter.AppToggleListener {
public abstract class AppsToggles extends Fragment implements AppsLoadListener,
AppsTogglesAdapter.AppToggleListener, SearchView.OnQueryTextListener {
private AppsTogglesAdapter mAdapter;
private SearchView mSearchView;
private TextView mEmptyText;
@Override
public View onCreateView(LayoutInflater inflater,
@ -61,18 +67,35 @@ public abstract class AppsToggles extends Fragment implements AppsLoadListener,
recyclerView.setAdapter(mAdapter);
mAdapter.setAppToggleListener(this);
TextView emptyAppsView = view.findViewById(R.id.no_apps);
emptyAppsView.setText(R.string.loading_apps);
recyclerView.setEmptyView(emptyAppsView);
mEmptyText = view.findViewById(R.id.no_apps);
mEmptyText.setText(R.string.loading_apps);
recyclerView.setEmptyView(mEmptyText);
(new AppsLoader((AppCompatActivity) requireActivity()))
.setAppsLoadListener(this)
.loadAllApps();
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater menuInflater) {
menuInflater.inflate(R.menu.search_menu, menu);
mSearchView = (SearchView) menu.findItem(R.id.search).getActionView();
mSearchView.setOnQueryTextListener(this);
}
@Override
public boolean onQueryTextSubmit(String query) { return true; }
@Override
public boolean onQueryTextChange(String newText) {
mAdapter.setFilter(newText);
return true;
}
@Override
public void onAppsInfoLoaded(List<AppDescriptor> apps) {
mAdapter.setApps(apps);
mEmptyText.setText(R.string.no_matches_found);
}
// Must be implemented in sub-classes

View File

@ -107,10 +107,7 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
public void onResume() {
super.onResume();
if((CaptureService.getConnsRegister() != null) || CaptureService.isServiceActive())
mEmptyText.setText(R.string.no_connections);
else
mEmptyText.setText(R.string.capture_not_running_status);
refreshEmptyText();
registerConnsListener();
mRecyclerView.setEmptyView(mEmptyText); // after registerConnsListener, when the adapter is populated
@ -146,6 +143,13 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
return inflater.inflate(R.layout.connections, container, false);
}
private void refreshEmptyText() {
if((CaptureService.getConnsRegister() != null) || CaptureService.isServiceActive())
mEmptyText.setText(mAdapter.hasFilter() ? R.string.no_matches_found : R.string.no_connections);
else
mEmptyText.setText(R.string.capture_not_running_status);
}
private void registerConnsListener() {
if (!listenerSet) {
ConnectionsRegister reg = CaptureService.getConnsRegister();
@ -810,6 +814,7 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
public boolean onQueryTextChange(String newText) {
mAdapter.setSearch(newText);
recheckScroll();
refreshEmptyText();
return true;
}

View File

@ -131,4 +131,10 @@ public class AppDescriptor implements Comparable<AppDescriptor>, Serializable {
return rv;
}
public boolean matches(String filter, boolean exactPackage) {
return getName().toLowerCase().contains(filter) ||
(exactPackage && getPackageName().equals(filter)) ||
(!exactPackage && getPackageName().contains(filter));
}
}

View File

@ -235,8 +235,7 @@ public class ConnectionDescriptor {
Integer.toString(uid).equals(filter) ||
Integer.toString(dst_port).contains(filter) ||
Integer.toString(src_port).equals(filter) ||
((app != null) && (app.getName().toLowerCase().contains(filter) ||
app.getPackageName().equals(filter)))
((app != null) && (app.matches(filter, true)))
);
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- NOTE: "always|collapseActionView" must be set to prevent the search button from
disappearing and to avoid hiding the other elements. -->
<item
android:id="@+id/search"
android:title="@string/search"
android:icon="@drawable/ic_search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always|collapseActionView"
tools:ignore="AlwaysShowAction" />
</menu>

View File

@ -374,4 +374,5 @@
<string name="notifications_notice">The app uses notifications to send alerts in case of anomalous events. Grant it the permission to send notifications in the next screen</string>
<string name="vpn_exceptions">VPN Exceptions</string>
<string name="vpn_exceptions_summary">Exclude some apps from the VPN connection. Their traffic will not be monitored</string>
<string name="no_matches_found">No matches</string>
</resources>