Optimize adapter ops with connections filters

This commit is contained in:
emanuele-f 2021-06-11 18:16:49 +02:00
parent c190caadfd
commit 2983be8ecf
8 changed files with 243 additions and 207 deletions

View File

@ -44,6 +44,7 @@ import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
@ -521,10 +522,18 @@ public class CaptureService extends VpnService implements Runnable {
INSTANCE.stop();
}
public static ConnectionsRegister getConnsRegister() {
public static @Nullable ConnectionsRegister getConnsRegister() {
return((INSTANCE != null) ? INSTANCE.conn_reg : null);
}
public static @NonNull ConnectionsRegister requireConnsRegister() {
ConnectionsRegister reg = getConnsRegister();
assert(reg != null);
return reg;
}
public static boolean isCapturingAsRoot() {
return((INSTANCE != null) &&
(INSTANCE.isRootCapture() == 1));

View File

@ -19,19 +19,16 @@
package com.emanuelef.remote_capture;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.preference.PreferenceManager;
import androidx.annotation.Nullable;
import com.emanuelef.remote_capture.interfaces.ConnectionsListener;
import com.emanuelef.remote_capture.model.AppDescriptor;
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 com.google.gson.Gson;
import java.util.ArrayList;
import java.util.Arrays;
@ -92,9 +89,11 @@ public class ConnectionsRegister {
int in_items = Math.min((mSize - mNumItems), conns.length);
int out_items = conns.length - in_items;
int insert_pos = mNumItems;
ConnectionDescriptor []removedItems = null;
if(out_items > 0) {
int pos = mTail;
removedItems = new ConnectionDescriptor[out_items];
// update the apps stats
for(int i=0; i<out_items; i++) {
@ -109,6 +108,7 @@ public class ConnectionsRegister {
mAppsStats.remove(uid);
}
removedItems[i] = conn;
pos = (pos + 1) % mSize;
}
}
@ -135,10 +135,10 @@ public class ConnectionsRegister {
for(ConnectionsListener listener: mListeners) {
if(out_items > 0)
listener.connectionsRemoved(0, out_items);
listener.connectionsRemoved(0, removedItems);
if(conns.length > 0)
listener.connectionsAdded(insert_pos - out_items, conns.length);
listener.connectionsAdded(insert_pos - out_items, conns);
}
}
@ -219,7 +219,11 @@ public class ConnectionsRegister {
return mUntrackedItems;
}
private synchronized ConnectionDescriptor getConnSimple(int i) {
public boolean hasExclusionFilter() {
return(mExclusionsEnabled && !mExclusions.isEmpty());
}
public @Nullable ConnectionDescriptor getConn(int i) {
if(i >= mNumItems)
return null;
@ -227,44 +231,7 @@ public class ConnectionsRegister {
return mItemsRing[pos];
}
private synchronized ConnectionDescriptor getConnWithFilter(int uidFilter, int target_pos) {
// pos is relative to the connections matching the provided uid / exclusions
int first = firstPos();
int virt_pos = 0;
for(int i = 0; i < mNumItems; i++) {
int pos = (first + i) % mSize;
ConnectionDescriptor item = mItemsRing[pos];
if(matches(item, uidFilter)) {
if(virt_pos == target_pos)
return item;
virt_pos++;
}
}
return null;
}
public boolean hasExclusionFilter() {
return(mExclusionsEnabled && !mExclusions.isEmpty());
}
private boolean matches(ConnectionDescriptor conn, int uidFilter) {
return((conn != null)
&& ((uidFilter == Utils.UID_NO_FILTER) || (conn.uid == uidFilter))
&& (!mExclusionsEnabled || !mExclusions.matches(conn)));
}
public ConnectionDescriptor getConn(int pos, int uidFilter) {
if((uidFilter == Utils.UID_NO_FILTER) && !hasExclusionFilter())
return getConnSimple(pos);
else
return getConnWithFilter(uidFilter, pos);
}
public synchronized int getConnPositionByIncrId(int incr_id) {
public synchronized int getConnPositionById(int incr_id) {
int first = firstPos();
for(int i = 0; i < mNumItems; i++) {
@ -302,66 +269,6 @@ public class ConnectionsRegister {
return rv;
}
public synchronized int getFilteredConnCount(int uidFilter) {
if(!hasExclusionFilter() && (uidFilter != Utils.UID_NO_FILTER)) {
// Optimized
AppStats stats = mAppsStats.get(uidFilter);
if(stats == null)
return 0;
return stats.num_connections;
} else {
// TODO optimize
int count = 0;
for(int i = 0; i < mNumItems; i++) {
ConnectionDescriptor item = mItemsRing[i];
if(matches(item, uidFilter))
count++;
}
return count;
}
}
public synchronized String dumpConnectionsCsv(Context context, int uidFilter) {
StringBuilder builder = new StringBuilder();
AppsResolver resolver = new AppsResolver(context);
// Header
builder.append(context.getString(R.string.connections_csv_fields_v1));
builder.append("\n");
// Contents
for(int i=0; i<getConnCount(); i++) {
ConnectionDescriptor conn = getConn(i, uidFilter);
if(conn != null) {
AppDescriptor app = resolver.get(conn.uid);
builder.append(conn.ipproto); builder.append(",");
builder.append(conn.src_ip); builder.append(",");
builder.append(conn.src_port); builder.append(",");
builder.append(conn.dst_ip); builder.append(",");
builder.append(conn.dst_port); builder.append(",");
builder.append(conn.uid); builder.append(",");
builder.append((app != null) ? app.getName() : ""); builder.append(",");
builder.append(conn.l7proto); builder.append(",");
builder.append(conn.getStatusLabel(context)); builder.append(",");
builder.append((conn.info != null) ? conn.info : ""); builder.append(",");
builder.append(conn.sent_bytes); builder.append(",");
builder.append(conn.rcvd_bytes); builder.append(",");
builder.append(conn.sent_pkts); builder.append(",");
builder.append(conn.rcvd_pkts); builder.append(",");
builder.append(conn.first_seen); builder.append(",");
builder.append(conn.last_seen); builder.append("\n");
}
}
return builder.toString();
}
public void saveExclusions() {
mPrefs.edit()
.putString(Prefs.PREF_EXCLUSIONS, mExclusions.toJson())

View File

@ -150,10 +150,10 @@ public class ConnectionDetailsActivity extends BaseActivity implements Connectio
ConnectionsRegister reg = CaptureService.getConnsRegister();
if((reg != null) && !mListenerSet) {
mConnPos = reg.getConnPositionByIncrId(mConn.incr_id);
mConnPos = reg.getConnPositionById(mConn.incr_id);
if(mConnPos != -1) {
ConnectionDescriptor conn = reg.getConn(mConnPos, Utils.UID_NO_FILTER);
ConnectionDescriptor conn = reg.getConn(mConnPos);
if(conn != null) {
if(conn.status < ConnectionDescriptor.CONN_STATUS_CLOSED) {
@ -238,10 +238,10 @@ public class ConnectionDetailsActivity extends BaseActivity implements Connectio
public void connectionsChanges(int num_connetions) {}
@Override
public void connectionsAdded(int start, int count) {}
public void connectionsAdded(int start, ConnectionDescriptor []conns) {}
@Override
public void connectionsRemoved(int start, int count) {}
public void connectionsRemoved(int start, ConnectionDescriptor []conns) {}
@Override
public void connectionsUpdated(int[] positions) {
@ -252,7 +252,7 @@ public class ConnectionDetailsActivity extends BaseActivity implements Connectio
for(int pos : positions) {
if(pos == mConnPos) {
ConnectionDescriptor conn = reg.getConn(pos, Utils.UID_NO_FILTER);
ConnectionDescriptor conn = reg.getConn(pos);
// Double check the incr_id
if((conn != null) && (conn.incr_id == mConn.incr_id))

View File

@ -21,6 +21,7 @@ package com.emanuelef.remote_capture.adapters;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -31,6 +32,7 @@ import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.emanuelef.remote_capture.interfaces.ConnectionsListener;
import com.emanuelef.remote_capture.model.AppDescriptor;
import com.emanuelef.remote_capture.CaptureService;
import com.emanuelef.remote_capture.AppsResolver;
@ -39,18 +41,24 @@ import com.emanuelef.remote_capture.ConnectionsRegister;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Objects;
public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.ViewHolder> {
public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.ViewHolder>
implements ConnectionsListener {
private static final String TAG = "ConnectionsAdapter";
private final LayoutInflater mLayoutInflater;
private final Drawable mUnknownIcon;
private int mItemCount;
private int mUnfilteredItemsCount;
private View.OnClickListener mListener;
private final AppsResolver mApps;
private final Context mContext;
private int mClickedPosition;
private int mUidFilter;
private int mNumRemovedItems;
private final HashMap<Integer, Integer> mIdToFilteredPos;
private ArrayList<ConnectionDescriptor> mFilteredConn;
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView icon;
@ -122,18 +130,17 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mUnknownIcon = ContextCompat.getDrawable(context, R.drawable.ic_image);
mListener = null;
mItemCount = 0;
mFilteredConn = null;
mUnfilteredItemsCount = 0;
mNumRemovedItems = 0;
mIdToFilteredPos = new HashMap<>();
mUidFilter = Utils.UID_NO_FILTER;
setHasStableIds(true);
}
public void setItemCount(int count) {
mItemCount = count;
}
@Override
public int getItemCount() {
return mItemCount;
return((mFilteredConn != null) ? mFilteredConn.size() : mUnfilteredItemsCount);
}
@NonNull
@ -174,13 +181,143 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
return ((conn != null) ? conn.incr_id : Utils.UID_UNKNOWN);
}
private boolean matches(ConnectionDescriptor conn, ConnectionsRegister reg) {
return((conn != null)
&& ((mUidFilter == Utils.UID_NO_FILTER) || (conn.uid == mUidFilter))
&& (!reg.mExclusionsEnabled || !reg.mExclusions.matches(conn)));
}
private int getFilteredItemPos(int incrId) {
Integer pos = mIdToFilteredPos.get(incrId);
if(pos == null)
return -1;
return(pos - mNumRemovedItems);
}
@Override
public void connectionsChanges(int num_connetions) {
mUnfilteredItemsCount = num_connetions;
refreshFilteredConnections();
}
@Override
public void connectionsAdded(int start, ConnectionDescriptor []conns) {
mUnfilteredItemsCount += conns.length;
if(mFilteredConn == null) {
notifyItemRangeInserted(start, conns.length);
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)) {
mIdToFilteredPos.put(conn.incr_id, vpos++);
mFilteredConn.add(conn);
numNew++;
}
}
if(numNew > 0)
notifyItemRangeInserted(mFilteredConn.size() - numNew, numNew);
}
@Override
public void connectionsRemoved(int start, ConnectionDescriptor []conns) {
mUnfilteredItemsCount -= conns.length;
if(mFilteredConn == null) {
notifyItemRangeRemoved(start, conns.length);
return;
}
for(ConnectionDescriptor conn: conns) {
if(conn == null)
continue;
int vpos = getFilteredItemPos(conn.incr_id);
if(vpos != -1) {
// Assume that connections are only remove from the start of the dataset
mFilteredConn.remove(0);
mIdToFilteredPos.remove(conn.incr_id);
mNumRemovedItems++;
notifyItemRemoved(vpos);
}
}
}
@Override
public void connectionsUpdated(int[] positions) {
if(mFilteredConn == null) {
for(int pos : positions)
notifyItemChanged(pos);
return;
}
ConnectionsRegister reg = CaptureService.requireConnsRegister();
for(int pos : positions) {
ConnectionDescriptor conn = reg.getConn(pos);
if(conn != null) {
int vpos = getFilteredItemPos(conn.incr_id);
if(vpos != -1) {
Log.d(TAG, "Changed item " + vpos + ", dataset size: " + getItemCount());
notifyItemChanged(pos);
}
}
}
}
public void refreshFilteredConnections() {
final ConnectionsRegister reg = CaptureService.getConnsRegister();
if(reg == null)
return;
Log.d(TAG, "refreshFilteredConn (" + mUnfilteredItemsCount + ") unfiltered");
mIdToFilteredPos.clear();
mNumRemovedItems = 0;
if((mUidFilter != Utils.UID_NO_FILTER) || reg.hasExclusionFilter()) {
int vpos = 0;
mFilteredConn = new ArrayList<>();
for(int i=0; i<mUnfilteredItemsCount; i++) {
ConnectionDescriptor conn = reg.getConn(i);
if(matches(conn, reg)) {
mFilteredConn.add(conn);
mIdToFilteredPos.put(conn.incr_id, vpos++);
}
}
Log.d(TAG, "refreshFilteredConn: " + mFilteredConn.size() + " connections matched");
} else
mFilteredConn = null;
notifyDataSetChanged();
}
public ConnectionDescriptor getItem(int pos) {
if(mFilteredConn != null)
return mFilteredConn.get(pos);
ConnectionsRegister reg = CaptureService.getConnsRegister();
if((pos < 0) || (pos >= getItemCount()) || (reg == null))
if((pos < 0) || (pos >= mUnfilteredItemsCount) || (reg == null))
return null;
return reg.getConn(pos, mUidFilter);
return reg.getConn(pos);
}
public void setClickListener(View.OnClickListener listener) {
@ -198,4 +335,41 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
public ConnectionDescriptor getClickedItem() {
return getItem(mClickedPosition);
}
public synchronized String dumpConnectionsCsv() {
StringBuilder builder = new StringBuilder();
AppsResolver resolver = new AppsResolver(mContext);
// Header
builder.append(mContext.getString(R.string.connections_csv_fields_v1));
builder.append("\n");
// Contents
for(int i=0; i<getItemCount(); i++) {
ConnectionDescriptor conn = getItem(i);
if(conn != null) {
AppDescriptor app = resolver.get(conn.uid);
builder.append(conn.ipproto); builder.append(",");
builder.append(conn.src_ip); builder.append(",");
builder.append(conn.src_port); builder.append(",");
builder.append(conn.dst_ip); builder.append(",");
builder.append(conn.dst_port); builder.append(",");
builder.append(conn.uid); builder.append(",");
builder.append((app != null) ? app.getName() : ""); builder.append(",");
builder.append(conn.l7proto); builder.append(",");
builder.append(conn.getStatusLabel(mContext)); builder.append(",");
builder.append((conn.info != null) ? conn.info : ""); builder.append(",");
builder.append(conn.sent_bytes); builder.append(",");
builder.append(conn.rcvd_bytes); builder.append(",");
builder.append(conn.sent_pkts); builder.append(",");
builder.append(conn.rcvd_pkts); builder.append(",");
builder.append(conn.first_seen); builder.append(",");
builder.append(conn.last_seen); builder.append("\n");
}
}
return builder.toString();
}
}

View File

@ -44,6 +44,7 @@ import com.emanuelef.remote_capture.activities.MainActivity;
import com.emanuelef.remote_capture.adapters.AppsStatsAdapter;
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.views.EmptyRecyclerView;
public class AppsFragment extends Fragment implements ConnectionsListener {
@ -181,12 +182,12 @@ public class AppsFragment extends Fragment implements ConnectionsListener {
}
@Override
public void connectionsAdded(int start, int count) {
public void connectionsAdded(int start, ConnectionDescriptor []conns) {
refreshAppsAsync();
}
@Override
public void connectionsRemoved(int start, int count) {
public void connectionsRemoved(int start, ConnectionDescriptor []conns) {
refreshAppsAsync();
}

View File

@ -57,20 +57,20 @@ import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.emanuelef.remote_capture.AppsResolver;
import com.emanuelef.remote_capture.CaptureService;
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.ExclusionsEditAdapter;
import com.emanuelef.remote_capture.model.AppDescriptor;
import com.emanuelef.remote_capture.CaptureService;
import com.emanuelef.remote_capture.model.AppState;
import com.emanuelef.remote_capture.AppsResolver;
import com.emanuelef.remote_capture.model.ConnectionDescriptor;
import com.emanuelef.remote_capture.activities.ConnectionDetailsActivity;
import com.emanuelef.remote_capture.adapters.ConnectionsAdapter;
import com.emanuelef.remote_capture.ConnectionsRegister;
import com.emanuelef.remote_capture.model.ConnectionsMatcher;
import com.emanuelef.remote_capture.views.EmptyRecyclerView;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.interfaces.ConnectionsListener;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
@ -344,7 +344,6 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
return super.onContextItemSelected(item);
reg.mExclusionsEnabled = true;
refreshExclusionsMenu();
refreshFilteredConnections();
return true;
}
@ -391,26 +390,10 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
mOldConnectionsText.setVisibility(View.GONE);
}
private boolean hasConnectionFilter() {
ConnectionsRegister reg = CaptureService.getConnsRegister();
return((mAdapter.getUidFilter() != Utils.UID_NO_FILTER) || ((reg != null) && reg.hasExclusionFilter()));
}
// This performs an unoptimized adapter refresh
private void refreshFilteredConnections() {
ConnectionsRegister reg = CaptureService.getConnsRegister();
int item_count;
int uid = mAdapter.getUidFilter();
if(reg != null)
item_count = reg.getFilteredConnCount(uid);
else
item_count = mAdapter.getItemCount();
Log.d(TAG, "New dataset size (uid=" +uid + "): " + item_count);
mAdapter.setItemCount(item_count);
mAdapter.notifyDataSetChanged();
mAdapter.refreshFilteredConnections();
refreshExclusionsMenu();
recheckScroll();
}
@ -420,15 +403,9 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
// in order to avoid desyncs
mHandler.post(() -> {
if(hasConnectionFilter()) {
refreshFilteredConnections();
return;
}
Log.d(TAG, "New connections size: " + num_connections);
Log.d(TAG, "New dataset size: " + num_connections);
mAdapter.setItemCount(num_connections);
mAdapter.notifyDataSetChanged();
mAdapter.connectionsChanges(num_connections);
recheckScroll();
if(autoScroll)
@ -437,22 +414,18 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
}
@Override
public void connectionsAdded(int start, int count) {
public void connectionsAdded(int start, ConnectionDescriptor []conns) {
mHandler.post(() -> {
Log.d(TAG, "Add " + count + " items at " + start);
Log.d(TAG, "Added " + conns.length + " connections at " + start);
if(!hasConnectionFilter()) {
mAdapter.setItemCount(mAdapter.getItemCount() + count);
mAdapter.notifyItemRangeInserted(start, count);
} else
refreshFilteredConnections();
mAdapter.connectionsAdded(start, conns);
if(autoScroll)
scrollToBottom();
ConnectionsRegister reg = CaptureService.getConnsRegister();
ConnectionsRegister reg = CaptureService.requireConnsRegister();
if((reg != null) && (reg.getUntrackedConnCount() > 0)) {
if(reg.getUntrackedConnCount() > 0) {
String info = String.format(getString(R.string.older_connections_notice), reg.getUntrackedConnCount());
mOldConnectionsText.setText(info);
@ -465,35 +438,16 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
}
@Override
public void connectionsRemoved(int start, int count) {
public void connectionsRemoved(int start,ConnectionDescriptor []conns) {
mHandler.post(() -> {
Log.d(TAG, "Remove " + count + " items at " + start);
if (!hasConnectionFilter()) {
mAdapter.setItemCount(mAdapter.getItemCount() - count);
mAdapter.notifyItemRangeRemoved(start, count);
} else
refreshFilteredConnections();
Log.d(TAG, "Remove " + conns.length + " connections at " + start);
mAdapter.connectionsRemoved(start, conns);
});
}
@Override
public void connectionsUpdated(int[] positions) {
mHandler.post(() -> {
if (hasConnectionFilter()) {
refreshFilteredConnections();
return;
}
int item_count = mAdapter.getItemCount();
for(int pos : positions) {
if(pos < item_count) {
Log.d(TAG, "Changed item " + pos + ", dataset size: " + mAdapter.getItemCount());
mAdapter.notifyItemChanged(pos);
}
}
});
mHandler.post(() -> mAdapter.connectionsUpdated(positions));
}
@Override
@ -537,10 +491,7 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
reg.mExclusionsEnabled = !reg.mExclusionsEnabled;
// Delay the refresh to wait for the menu to be closed
(new Handler(requireActivity().getMainLooper())).postDelayed(() -> {
refreshExclusionsMenu();
refreshFilteredConnections();
}, 50);
(new Handler(requireActivity().getMainLooper())).postDelayed(this::refreshFilteredConnections, 50);
return true;
} else if(id == R.id.delete_exclusions) {
@ -644,12 +595,7 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
}
private void dumpCsv() {
ConnectionsRegister reg = CaptureService.getConnsRegister();
if(reg == null)
return;
String dump = reg.dumpConnectionsCsv(requireContext(), mAdapter.getUidFilter());
String dump = mAdapter.dumpConnectionsCsv();
if(mCsvFname != null) {
Log.d(TAG, "Writing CSV file: " + mCsvFname);
@ -736,9 +682,7 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
builder.setTitle(R.string.edit_exclusions);
builder.setView(exclListView);
builder.setPositiveButton(R.string.ok, (dialog, which) -> {
updateExclusions(adapter);
});
builder.setPositiveButton(R.string.ok, (dialog, which) -> updateExclusions(adapter));
builder.setNeutralButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
final AlertDialog alert = builder.create();
@ -766,10 +710,8 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
}
}
if(changed) {
refreshExclusionsMenu();
if(changed)
refreshFilteredConnections();
}
}
private void deleteExclusions() {
@ -779,7 +721,6 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
return;
reg.mExclusions.clear();
refreshExclusionsMenu();
refreshFilteredConnections();
}

View File

@ -19,9 +19,11 @@
package com.emanuelef.remote_capture.interfaces;
import com.emanuelef.remote_capture.model.ConnectionDescriptor;
public interface ConnectionsListener {
void connectionsChanges(int num_connetions);
void connectionsAdded(int start, int count);
void connectionsRemoved(int start, int count);
void connectionsAdded(int start, ConnectionDescriptor []conns);
void connectionsRemoved(int start, ConnectionDescriptor []conns);
void connectionsUpdated(int[] positions);
}

View File

@ -22,7 +22,8 @@
<TextView
android:id="@+id/no_connections"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center_horizontal"
android:layout_marginTop="40dp"
android:textStyle="italic"
@ -33,7 +34,8 @@
<com.emanuelef.remote_capture.views.EmptyRecyclerView
android:id="@+id/connections_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scrollbars="vertical"
android:scrollbarStyle="outsideOverlay"
android:fillViewport="true" />