Rework whitelist

The whitelist editor is now a separate activity. Whitelist removed from
the ConnectionRegister.
This commit is contained in:
emanuele-f 2021-06-14 15:21:57 +02:00
parent 9b7675d6e2
commit f49c34ddec
23 changed files with 402 additions and 212 deletions

View File

@ -62,6 +62,10 @@
android:name=".activities.LogviewActivity"
android:launchMode="singleTop"
android:parentActivityName=".activities.MainActivity" />
<activity
android:name=".activities.WhitelistActivity"
android:launchMode="singleTop"
android:parentActivityName=".activities.MainActivity" />
<service
android:name=".CaptureService"

View File

@ -196,7 +196,7 @@ public class CaptureService extends VpnService implements Runnable {
last_bytes = 0;
last_connections = 0;
root_capture = Prefs.isRootCaptureEnabled(prefs);
conn_reg = new ConnectionsRegister(CONNECTIONS_LOG_SIZE, this, prefs);
conn_reg = new ConnectionsRegister(CONNECTIONS_LOG_SIZE);
if(dump_mode != Prefs.DumpMode.HTTP_SERVER)
mHttpServer = null;
@ -432,9 +432,6 @@ public class CaptureService extends VpnService implements Runnable {
private void stop() {
stopPacketLoop();
if(conn_reg != null)
conn_reg.saveWhitelist();
while((mThread != null) && (mThread.isAlive())) {
try {
Log.d(TAG, "Joining native thread...");

View File

@ -19,8 +19,6 @@
package com.emanuelef.remote_capture;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.Nullable;
@ -28,8 +26,6 @@ import androidx.annotation.Nullable;
import com.emanuelef.remote_capture.interfaces.ConnectionsListener;
import com.emanuelef.remote_capture.model.AppStats;
import com.emanuelef.remote_capture.model.ConnectionDescriptor;
import com.emanuelef.remote_capture.model.ConnectionsMatcher;
import com.emanuelef.remote_capture.model.Prefs;
import java.util.ArrayList;
import java.util.Arrays;
@ -49,11 +45,8 @@ public class ConnectionsRegister {
private int mUntrackedItems;
private final Map<Integer, AppStats> mAppsStats;
private final ArrayList<ConnectionsListener> mListeners;
private final SharedPreferences mPrefs;
public final ConnectionsMatcher mWhitelist;
public boolean mWhitelistEnabled;
public ConnectionsRegister(int _size, Context context, SharedPreferences prefs) {
public ConnectionsRegister(int _size) {
mTail = 0;
mNumItems = 0;
mUntrackedItems = 0;
@ -61,15 +54,6 @@ public class ConnectionsRegister {
mItemsRing = new ConnectionDescriptor[mSize];
mListeners = new ArrayList<>();
mAppsStats = new HashMap<>(); // uid -> AppStats
mWhitelistEnabled = true;
mPrefs = prefs;
mWhitelist = new ConnectionsMatcher(context);
// Try to restore the whitelist
String serialized = prefs.getString(Prefs.PREF_WHITELIST, "");
if(!serialized.isEmpty())
mWhitelist.fromJson(serialized);
}
private int firstPos() {
@ -220,10 +204,6 @@ public class ConnectionsRegister {
return mUntrackedItems;
}
public boolean hasWhitelistFilter() {
return(mWhitelistEnabled && !mWhitelist.isEmpty());
}
public @Nullable ConnectionDescriptor getConn(int i) {
if(i >= mNumItems)
return null;
@ -269,10 +249,4 @@ public class ConnectionsRegister {
return rv;
}
public void saveWhitelist() {
mPrefs.edit()
.putString(Prefs.PREF_WHITELIST, mWhitelist.toJson())
.apply();
}
}

View File

@ -347,6 +347,9 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
startActivity(intent);
} else
Utils.showToast(this, R.string.capture_not_started);
} else if(id == R.id.edit_whitelist) {
Intent intent = new Intent(MainActivity.this, WhitelistActivity.class);
startActivity(intent);
} else if(id == R.id.open_root_log) {
Intent intent = new Intent(MainActivity.this, LogviewActivity.class);
startActivity(intent);

View File

@ -0,0 +1,42 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PCAPdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2020-21 - Emanuele Faranda
*/
package com.emanuelef.remote_capture.activities;
import android.os.Bundle;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.fragments.AppsFragment;
import com.emanuelef.remote_capture.fragments.WhitelistFragment;
public class WhitelistActivity extends BaseActivity {
private static final String TAG = "WhitelistActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.whitelist);
setContentView(R.layout.whitelist_activity);
getSupportFragmentManager().beginTransaction()
.replace(R.id.whitelist_fragment, new WhitelistFragment())
.commit();
}
}

View File

@ -40,6 +40,7 @@ import com.emanuelef.remote_capture.model.ConnectionDescriptor;
import com.emanuelef.remote_capture.ConnectionsRegister;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.Utils;
import com.emanuelef.remote_capture.model.Whitelist;
import java.util.ArrayList;
import java.util.HashMap;
@ -57,8 +58,10 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
private int mClickedPosition;
private int mUidFilter;
private int mNumRemovedItems;
public boolean mWhitelistEnabled;
private final HashMap<Integer, Integer> mIdToFilteredPos;
private ArrayList<ConnectionDescriptor> mFilteredConn;
public final Whitelist mWhitelist;
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView icon;
@ -135,7 +138,11 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
mNumRemovedItems = 0;
mIdToFilteredPos = new HashMap<>();
mUidFilter = Utils.UID_NO_FILTER;
mWhitelistEnabled = true;
mWhitelist = new Whitelist(context);
setHasStableIds(true);
mWhitelist.reload();
}
@Override
@ -181,10 +188,10 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
return ((conn != null) ? conn.incr_id : Utils.UID_UNKNOWN);
}
private boolean matches(ConnectionDescriptor conn, ConnectionsRegister reg) {
private boolean matches(ConnectionDescriptor conn) {
return((conn != null)
&& ((mUidFilter == Utils.UID_NO_FILTER) || (conn.uid == mUidFilter))
&& (!reg.mWhitelistEnabled || !reg.mWhitelist.matches(conn)));
&& (!mWhitelistEnabled || !mWhitelist.matches(conn)));
}
private int getFilteredItemPos(int incrId) {
@ -211,13 +218,12 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
return;
}
ConnectionsRegister reg = CaptureService.requireConnsRegister();
int numNew = 0;
int vpos = mNumRemovedItems + mFilteredConn.size();
// Assume that connections are only added at the end of the dataset
for(ConnectionDescriptor conn : conns) {
if(matches(conn, reg)) {
if(matches(conn)) {
mIdToFilteredPos.put(conn.incr_id, vpos++);
mFilteredConn.add(conn);
numNew++;
@ -288,14 +294,14 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
mIdToFilteredPos.clear();
mNumRemovedItems = 0;
if((mUidFilter != Utils.UID_NO_FILTER) || reg.hasWhitelistFilter()) {
if((mUidFilter != Utils.UID_NO_FILTER) || hasWhitelistFilter()) {
int vpos = 0;
mFilteredConn = new ArrayList<>();
for(int i=0; i<mUnfilteredItemsCount; i++) {
ConnectionDescriptor conn = reg.getConn(i);
if(matches(conn, reg)) {
if(matches(conn)) {
mFilteredConn.add(conn);
mIdToFilteredPos.put(conn.incr_id, vpos++);
}
@ -336,6 +342,10 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
return getItem(mClickedPosition);
}
public boolean hasWhitelistFilter() {
return (mWhitelistEnabled && !mWhitelist.isEmpty());
}
public synchronized String dumpConnectionsCsv() {
StringBuilder builder = new StringBuilder();
AppsResolver resolver = new AppsResolver(mContext);

View File

@ -36,52 +36,26 @@ import java.util.Iterator;
public class WhitelistEditAdapter extends ArrayAdapter<ConnectionsMatcher.Item> {
private final LayoutInflater mLayoutInflater;
private boolean mShowTrash;
private final int mResId;
public WhitelistEditAdapter(Context context, int res, Iterator<ConnectionsMatcher.Item> items) {
super(context, res);
mResId = res;
mShowTrash = true;
public WhitelistEditAdapter(Context context, Iterator<ConnectionsMatcher.Item> items) {
super(context, R.layout.whitelist_item);
mLayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
while(items.hasNext()) {
ConnectionsMatcher.Item item = items.next();
add(item);
}
recheckSize();
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
if(convertView == null)
convertView = mLayoutInflater.inflate(mResId, parent, false);
convertView = mLayoutInflater.inflate(R.layout.whitelist_item, parent, false);
ConnectionsMatcher.Item item = getItem(position);
((TextView)convertView.findViewById(R.id.item_label)).setText(item.getLabel());
convertView.findViewById(R.id.item_icon).setVisibility(mShowTrash ? View.VISIBLE : View.INVISIBLE);
return convertView;
}
@Override
public void remove(@Nullable ConnectionsMatcher.Item object) {
super.remove(object);
recheckSize();
}
private void recheckSize() {
if(getCount() == 1) {
// Prevent an empty view
mShowTrash = false;
notifyDataSetChanged();
} else if(!mShowTrash) {
mShowTrash = true;
notifyDataSetChanged();
}
}
}

View File

@ -38,7 +38,6 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
@ -47,7 +46,6 @@ import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@ -61,7 +59,6 @@ import com.emanuelef.remote_capture.ConnectionsRegister;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.Utils;
import com.emanuelef.remote_capture.activities.MainActivity;
import com.emanuelef.remote_capture.adapters.WhitelistEditAdapter;
import com.emanuelef.remote_capture.model.AppDescriptor;
import com.emanuelef.remote_capture.model.AppState;
import com.emanuelef.remote_capture.model.ConnectionDescriptor;
@ -77,7 +74,6 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
@ -92,7 +88,6 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
private boolean autoScroll;
private boolean listenerSet;
private MenuItem mMenuItemAppSel;
private MenuItem mMenuItemWhitelist;
private MenuItem mMenuItemEnableWhitelist;
private MenuItem mMenuItemDisableWhitelist;
private MenuItem mSave;
@ -110,11 +105,11 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
public void onResume() {
super.onResume();
registerConnsListener();
// Reload the whitelist as it could modified in WhitelistActivity
mAdapter.mWhitelist.reload();
// reg.mWhitelistEnabled may have changed (e.g. when filtering from the AppsActivity
if(mMenuItemWhitelist != null)
refreshWhitelistMenu();
registerConnsListener();
refreshMenuIcons();
}
@Override
@ -129,6 +124,7 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
super.onSaveInstanceState(outState);
outState.putInt("uidFilter", mAdapter.getUidFilter());
outState.putBoolean("whitelistEnabled", mAdapter.mWhitelistEnabled);
}
@Override
@ -232,22 +228,23 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
if(uidFilter != Utils.UID_NO_FILTER) {
// "consume" it
intent.removeExtra(MainActivity.UID_FILTER_EXTRA);
// disable the whitelist to prevent an empty view
ConnectionsRegister reg = CaptureService.getConnsRegister();
if(reg != null)
reg.mWhitelistEnabled = false;
}
}
if ((uidFilter == Utils.UID_NO_FILTER) && (savedInstanceState != null)) {
uidFilter = savedInstanceState.getInt("uidFilter", Utils.UID_NO_FILTER);
if(savedInstanceState != null) {
if(uidFilter == Utils.UID_NO_FILTER)
uidFilter = savedInstanceState.getInt("uidFilter", Utils.UID_NO_FILTER);
mAdapter.mWhitelistEnabled = savedInstanceState.getBoolean("whitelistEnabled", true);
}
if(uidFilter != Utils.UID_NO_FILTER)
if(uidFilter != Utils.UID_NO_FILTER) {
setUidFilter(uidFilter);
// Avoid hiding the interesting items
mAdapter.mWhitelistEnabled = false;
}
// Register for service status
mReceiver = new BroadcastReceiver() {
@Override
@ -330,29 +327,29 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
ConnectionsRegister reg = CaptureService.getConnsRegister();
ConnectionDescriptor conn = mAdapter.getClickedItem();
if((reg == null) || (conn == null))
if(conn == null)
return super.onContextItemSelected(item);
int id = item.getItemId();
String label = item.getTitle().toString();
if(id == R.id.exclude_app)
reg.mWhitelist.addApp(conn.uid, label);
mAdapter.mWhitelist.addApp(conn.uid, label);
else if(id == R.id.exclude_host)
reg.mWhitelist.addHost(conn.info, label);
mAdapter.mWhitelist.addHost(conn.info, label);
else if(id == R.id.exclude_ip)
reg.mWhitelist.addIp(conn.dst_ip, label);
mAdapter.mWhitelist.addIp(conn.dst_ip, label);
else if(id == R.id.exclude_proto)
reg.mWhitelist.addProto(conn.l7proto, label);
mAdapter.mWhitelist.addProto(conn.l7proto, label);
else if(id == R.id.exclude_root_domain)
reg.mWhitelist.addRootDomain(Utils.getRootDomain(conn.info), label);
mAdapter.mWhitelist.addRootDomain(Utils.getRootDomain(conn.info), label);
else
return super.onContextItemSelected(item);
reg.mWhitelistEnabled = true;
mAdapter.mWhitelist.save();
mAdapter.mWhitelistEnabled = true;
refreshFilteredConnections();
return true;
}
@ -402,7 +399,7 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
// This performs an unoptimized adapter refresh
private void refreshFilteredConnections() {
mAdapter.refreshFilteredConnections();
refreshWhitelistMenu();
refreshMenuIcons();
recheckScroll();
}
@ -465,14 +462,12 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
mSave = menu.findItem(R.id.save);
mMenuItemAppSel = menu.findItem(R.id.action_show_app_filter);
mMenuItemWhitelist = menu.findItem(R.id.whitelist);
mMenuItemEnableWhitelist = menu.findItem(R.id.enable_whitelist);
mMenuItemDisableWhitelist = menu.findItem(R.id.disable_whitelist);
mMenuItemEnableWhitelist = menu.findItem(R.id.hide_whitelist);
mMenuItemDisableWhitelist = menu.findItem(R.id.show_whitelist);
mFilterIcon = mMenuItemAppSel.getIcon();
refreshFilterIcon();
refreshMenuIcons();
refreshWhitelistMenu();
}
@Override
@ -489,15 +484,12 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
} else if(id == R.id.save) {
openFileSelector();
return true;
} else if(id == R.id.edit_whitelist) {
showWhitelistEditor();
return true;
} else if((id == R.id.enable_whitelist) || (id == R.id.disable_whitelist)) {
} else if((id == R.id.hide_whitelist) || (id == R.id.show_whitelist)) {
ConnectionsRegister reg = CaptureService.getConnsRegister();
if(reg == null)
return false;
reg.mWhitelistEnabled = !reg.mWhitelistEnabled;
mAdapter.mWhitelistEnabled = !mAdapter.mWhitelistEnabled;
// Delay the refresh to wait for the menu to be closed
(new Handler(requireActivity().getMainLooper())).postDelayed(this::refreshFilteredConnections, 50);
@ -576,25 +568,14 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
mMenuItemAppSel.setEnabled(is_enabled);
mSave.setEnabled(is_enabled);
}
private void refreshWhitelistMenu() {
ConnectionsRegister reg = CaptureService.getConnsRegister();
if(reg == null) {
mMenuItemWhitelist.setVisible(false);
return;
if((mAdapter == null) || mAdapter.mWhitelist.isEmpty()) {
mMenuItemDisableWhitelist.setVisible(false);
mMenuItemEnableWhitelist.setVisible(false);
} else {
mMenuItemDisableWhitelist.setVisible(mAdapter.mWhitelistEnabled);
mMenuItemEnableWhitelist.setVisible(!mAdapter.mWhitelistEnabled);
}
// Update the icon only if something changed
// NOTE: getApplicationContext required to properly style the tint
mMenuItemWhitelist.setIcon(
ContextCompat.getDrawable(requireContext().getApplicationContext(),
reg.mWhitelistEnabled ? R.drawable.ic_eye_slash : R.drawable.ic_eye));
mMenuItemWhitelist.setVisible(!reg.mWhitelist.isEmpty());
mMenuItemDisableWhitelist.setVisible(reg.mWhitelistEnabled);
mMenuItemEnableWhitelist.setVisible(!reg.mWhitelistEnabled);
}
private void dumpCsv() {
@ -663,59 +644,6 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
}
}
private void showWhitelistEditor() {
ConnectionsRegister reg = CaptureService.getConnsRegister();
if(reg == null)
return;
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
WhitelistEditAdapter adapter = new WhitelistEditAdapter(requireContext(),
R.layout.whitelist_item, reg.mWhitelist.iterItems());
View exclListView = requireActivity().getLayoutInflater().inflate(R.layout.whitelist, null);
ListView whitelist = exclListView.findViewById(R.id.list);
whitelist.setAdapter(adapter);
whitelist.setOnItemClickListener((parent, view, position, id) -> {
if(adapter.getCount() > 1)
adapter.remove(adapter.getItem(position));
});
builder.setTitle(R.string.edit_whitelist);
builder.setView(exclListView);
builder.setPositiveButton(R.string.ok, (dialog, which) -> updateWhitelist(adapter));
builder.setNeutralButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
final AlertDialog alert = builder.create();
alert.setCanceledOnTouchOutside(true);
alert.show();
}
private void updateWhitelist(WhitelistEditAdapter adapter) {
ConnectionsRegister reg = CaptureService.getConnsRegister();
ArrayList<ConnectionsMatcher.Item> toRemove = new ArrayList<>();
if(reg == null)
return;
Iterator<ConnectionsMatcher.Item> iter = reg.mWhitelist.iterItems();
boolean changed = false;
// Remove the whitelisted items which are not in the adapter dataset
while(iter.hasNext()) {
ConnectionsMatcher.Item item = iter.next();
if(adapter.getPosition(item) < 0)
toRemove.add(item);
}
if(toRemove.size() > 0) {
reg.mWhitelist.removeItems(toRemove);
refreshFilteredConnections();
}
}
private void csvFileResult(final ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
mCsvFname = result.getData().getData();

View File

@ -0,0 +1,160 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PCAPdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2020-21 - Emanuele Faranda
*/
package com.emanuelef.remote_capture.fragments;
import android.app.AlertDialog;
import android.os.Bundle;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.adapters.WhitelistEditAdapter;
import com.emanuelef.remote_capture.model.ConnectionsMatcher;
import com.emanuelef.remote_capture.model.Whitelist;
import java.util.ArrayList;
import java.util.Iterator;
public class WhitelistFragment extends Fragment {
private WhitelistEditAdapter mAdapter;
private TextView mEmptyText;
private ArrayList<ConnectionsMatcher.Item> mSelected = new ArrayList<>();
private Whitelist mWhitelist;
private ListView mWhitelistView;
private static final String TAG = "WhitelistFragment";
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.whitelist_fragment, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
mWhitelistView = view.findViewById(R.id.whitelist);
mEmptyText = view.findViewById(R.id.whitelist_empty);
mWhitelist = new Whitelist(view.getContext());
mWhitelist.reload();
mAdapter = new WhitelistEditAdapter(requireContext(), mWhitelist.iterItems());
mWhitelistView.setAdapter(mAdapter);
mWhitelistView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mWhitelistView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
ConnectionsMatcher.Item item = mAdapter.getItem(position);
if(checked)
mSelected.add(item);
else
mSelected.remove(item);
mode.setTitle(getString(R.string.n_selected, mSelected.size()));
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = requireActivity().getMenuInflater();
inflater.inflate(R.menu.whitelist_cab, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem menuItem) {
int id = menuItem.getItemId();
if(id == R.id.delete_entry) {
if(mSelected.size() >= mAdapter.getCount()) {
mAdapter.clear();
mWhitelist.clear();
mWhitelist.save();
} else {
for(ConnectionsMatcher.Item item : mSelected)
mAdapter.remove(item);
updateWhitelist();
}
mode.finish();
recheckWhitelistSize();
return true;
} else if(id == R.id.select_all) {
if(mSelected.size() >= mAdapter.getCount())
mode.finish();
else {
for(int i=0; i<mAdapter.getCount(); i++) {
if(!mWhitelistView.isItemChecked(i))
mWhitelistView.setItemChecked(i, true);
}
}
return true;
} else
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mSelected = new ArrayList<>();
}
});
recheckWhitelistSize();
}
private void recheckWhitelistSize() {
mEmptyText.setVisibility((mAdapter.getCount() == 0) ? View.VISIBLE : View.GONE);
}
private void updateWhitelist() {
ArrayList<ConnectionsMatcher.Item> toRemove = new ArrayList<>();
Iterator<ConnectionsMatcher.Item> iter = mWhitelist.iterItems();
// Remove the whitelisted items which are not in the adapter dataset
while(iter.hasNext()) {
ConnectionsMatcher.Item item = iter.next();
if (mAdapter.getPosition(item) < 0)
toRemove.add(item);
}
if(toRemove.size() > 0) {
mWhitelist.removeItems(toRemove);
mWhitelist.save();
}
}
}

View File

@ -22,7 +22,6 @@ package com.emanuelef.remote_capture.model;
import android.content.Context;
import android.graphics.Typeface;
import android.text.style.StyleSpan;
import android.util.Log;
import androidx.annotation.Nullable;
@ -135,6 +134,8 @@ public class ConnectionsMatcher {
private void deserialize(JsonObject object) {
mItems = new ArrayList<>();
mMatches.clear();
JsonArray itemArray = object.getAsJsonArray("items");
AppsResolver resolver = new AppsResolver(mContext);
@ -176,7 +177,6 @@ public class ConnectionsMatcher {
private void addItem(Item item) {
String key = matchKey(item.getType(), item.getValue().toString());
Log.d(TAG, key);
if(!mMatches.containsKey(key)) {
mItems.add(item);
@ -217,7 +217,7 @@ public class ConnectionsMatcher {
}
public String toJson() {
Gson gson = new GsonBuilder().registerTypeAdapter(ConnectionsMatcher.class, new Serializer())
Gson gson = new GsonBuilder().registerTypeAdapter(getClass(), new Serializer())
.create();
String serialized = gson.toJson(this);

View File

@ -0,0 +1,49 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PCAPdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2020-21 - Emanuele Faranda
*/
package com.emanuelef.remote_capture.model;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.preference.PreferenceManager;
public class Whitelist extends ConnectionsMatcher {
private final SharedPreferences mPrefs;
public Whitelist(Context ctx) {
super(ctx);
mPrefs = PreferenceManager.getDefaultSharedPreferences(ctx);
}
public void reload() {
// Try to restore the whitelist
String serialized = mPrefs.getString(Prefs.PREF_WHITELIST, "");
if(!serialized.isEmpty())
fromJson(serialized);
}
public void save() {
mPrefs.edit()
.putString(Prefs.PREF_WHITELIST, toJson())
.apply();
}
}

View File

@ -83,6 +83,8 @@ public class EmptyRecyclerView extends RecyclerView {
if (adapter != null) {
adapter.registerAdapterDataObserver(observer);
}
initEmptyView();
}
public void setEmptyView(View view) {

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M22,7h-9v2h9V7zM22,15h-9v2h9V15zM5.54,11L2,7.46l1.41,-1.41l2.12,2.12l4.24,-4.24l1.41,1.41L5.54,11zM5.54,19L2,15.46l1.41,-1.41l2.12,2.12l4.24,-4.24l1.41,1.41L5.54,19z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z"/>
</vector>

View File

@ -4,11 +4,11 @@
android:layout_height="match_parent">
<com.emanuelef.remote_capture.views.EmptyRecyclerView
android:id="@+id/apps_stats_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:scrollbarStyle="outsideOverlay"
android:id="@+id/apps_stats_view"
android:fillViewport="true" />
<TextView

View File

@ -2,11 +2,10 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="8dp">
android:orientation="vertical">
<ListView
android:id="@+id/list"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/whitelist_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/whitelist"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbarStyle="outsideOverlay" />
<TextView
android:id="@+id/whitelist_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_marginTop="40dp"
android:textStyle="italic"
android:textSize="15sp"
android:text="@string/whitelist_empty">
</TextView>
</RelativeLayout>

View File

@ -1,25 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- For some unknown reason, on some devices using a LinearLayout causes each row not to take all
the available space when the scrollbar is shown. -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:padding="12sp">
android:background="?android:attr/activatedBackgroundIndicator"
android:orientation="vertical"
android:padding="15sp">
<TextView
android:id="@+id/item_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="App: example app"
android:gravity="center_vertical"
android:layout_toStartOf="@id/item_icon" />
<ImageView
android:id="@+id/item_icon"
android:src="@drawable/ic_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"/>
</RelativeLayout>
android:gravity="center_vertical" />
</LinearLayout>

View File

@ -11,23 +11,19 @@
app:showAsAction="ifRoom" />
<item
android:id="@+id/whitelist"
android:title="@string/whitelist"
android:id="@+id/hide_whitelist"
android:title="@string/hide_whitelisted"
android:icon="@drawable/ic_eye"
android:orderInCategory="20"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/show_whitelist"
android:title="@string/show_whitelisted"
android:icon="@drawable/ic_eye_slash"
app:showAsAction="ifRoom">
<menu>
<item
android:title="@string/hide_whitelisted"
android:id="@+id/enable_whitelist" />
<item
android:title="@string/show_whitelisted"
android:id="@+id/disable_whitelist" />
<item
android:title="@string/edit"
android:id="@+id/edit_whitelist" />
</menu>
</item>
android:orderInCategory="20"
app:showAsAction="ifRoom"
android:visible="false" />
<item
android:id="@+id/save"

View File

@ -9,6 +9,10 @@
android:id="@+id/action_stats"
android:title="@string/stats"
android:icon="@drawable/ic_list" />
<item
android:id="@+id/edit_whitelist"
android:title="@string/whitelist"
android:icon="@drawable/ic_checklist" />
<item
android:id="@+id/open_root_log"
android:title="@string/root_log"

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/select_all"
android:title="@string/ic_select_all"
android:orderInCategory="10"
android:icon="@drawable/ic_select_all"
app:showAsAction="ifRoom" />
<item
android:id="@+id/delete_entry"
android:title="@string/delete"
android:orderInCategory="20"
android:icon="@android:drawable/ic_menu_delete"
app:showAsAction="ifRoom" />
</menu>

View File

@ -135,5 +135,8 @@
<string name="whitelist">Esclusioni</string>
<string name="edit_whitelist">Modifica Esclusioni</string>
<string name="whitelist_action">Escludi …</string>
<string name="whitelist_empty">Lista esclusioni vuota.\nPuoi escludere una connessione tenendo premuto su di essa.</string>
<string name="n_selected">%1$d selezionati</string>
<string name="ic_select_all">Seleziona tutto</string>
</resources>

View File

@ -139,10 +139,13 @@
<string name="cancel">Cancel</string>
<string name="edit">Edit</string>
<string name="delete_all">Delete all</string>
<string name="show_whitelisted">Show whitelisted</string>
<string name="hide_whitelisted">Hide whitelisted</string>
<string name="show_whitelisted">Show whitelisted connections</string>
<string name="hide_whitelisted">Hide whitelisted connections</string>
<string name="whitelist">Whitelist</string>
<string name="edit_whitelist">Edit Whitelist</string>
<string name="whitelist_action">Whitelist …</string>
<string name="whitelist_empty">The whitelist is empty.\nYou can long press a connection to whitelist it.</string>
<string name="n_selected">%1$d selected</string>
<string name="ic_select_all">Select all</string>
</resources>