From 2983be8ecfcc556e10af73bf2e2e42c0a6aeccd2 Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Fri, 11 Jun 2021 18:16:49 +0200 Subject: [PATCH] Optimize adapter ops with connections filters --- .../remote_capture/CaptureService.java | 11 +- .../remote_capture/ConnectionsRegister.java | 117 ++--------- .../activities/ConnectionDetailsActivity.java | 10 +- .../adapters/ConnectionsAdapter.java | 194 +++++++++++++++++- .../fragments/AppsFragment.java | 5 +- .../fragments/ConnectionsFragment.java | 101 ++------- .../interfaces/ConnectionsListener.java | 6 +- app/src/main/res/layout/connections.xml | 6 +- 8 files changed, 243 insertions(+), 207 deletions(-) diff --git a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java index 7c30ec0e..3a728c7e 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java +++ b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java @@ -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)); diff --git a/app/src/main/java/com/emanuelef/remote_capture/ConnectionsRegister.java b/app/src/main/java/com/emanuelef/remote_capture/ConnectionsRegister.java index 4d33127e..a8754c5a 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/ConnectionsRegister.java +++ b/app/src/main/java/com/emanuelef/remote_capture/ConnectionsRegister.java @@ -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 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 { +public class ConnectionsAdapter extends RecyclerView.Adapter + 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 mIdToFilteredPos; + private ArrayList mFilteredConn; public static class ViewHolder extends RecyclerView.ViewHolder { ImageView icon; @@ -122,18 +130,17 @@ public class ConnectionsAdapter extends RecyclerView.Adapter(); 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 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= 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 { - 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(); } diff --git a/app/src/main/java/com/emanuelef/remote_capture/interfaces/ConnectionsListener.java b/app/src/main/java/com/emanuelef/remote_capture/interfaces/ConnectionsListener.java index 9788085c..9c92676a 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/interfaces/ConnectionsListener.java +++ b/app/src/main/java/com/emanuelef/remote_capture/interfaces/ConnectionsListener.java @@ -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); } diff --git a/app/src/main/res/layout/connections.xml b/app/src/main/res/layout/connections.xml index 676f8277..683de7ea 100644 --- a/app/src/main/res/layout/connections.xml +++ b/app/src/main/res/layout/connections.xml @@ -22,7 +22,8 @@