mirror of
https://github.com/emanuele-f/PCAPdroid.git
synced 2026-07-03 21:21:12 +08:00
Add ability to sort apps
Apps can now be sorted by name and total/sent/received bytes. The app sent/received bytes are now shown. Closes #245
This commit is contained in:
parent
28aa972f69
commit
3830f93054
@ -702,7 +702,9 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
|
||||
startExportSslkeylogfile();
|
||||
});
|
||||
|
||||
builder.create().show();
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void deletePcapFile(Uri pcapUri) {
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
|
||||
package com.emanuelef.remote_capture.adapters;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@ -61,6 +62,14 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
|
||||
private List<AppStats> mStats;
|
||||
private final AppsResolver mApps;
|
||||
private AppStats mSelectedItem;
|
||||
private SortField mSortField;
|
||||
|
||||
public enum SortField {
|
||||
NAME,
|
||||
TOTAL_BYTES,
|
||||
BYTES_SENT,
|
||||
BYTES_RCVD
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ImageView icon;
|
||||
@ -68,6 +77,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
|
||||
ImageView whitelistedFlag;
|
||||
ImageView tempUnblocked;
|
||||
TextView info;
|
||||
TextView sent_rcvd;
|
||||
TextView traffic;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
@ -78,6 +88,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
|
||||
whitelistedFlag = itemView.findViewById(R.id.whitelisted);
|
||||
tempUnblocked = itemView.findViewById(R.id.temp_unblocked);
|
||||
info = itemView.findViewById(R.id.app_info);
|
||||
sent_rcvd = itemView.findViewById(R.id.sent_rcvd);
|
||||
traffic = itemView.findViewById(R.id.traffic);
|
||||
}
|
||||
|
||||
@ -102,6 +113,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
|
||||
boolean isWhitelistedApp = mWhitelist.matchesApp(stats.getUid());
|
||||
boolean isWhitelistEnabled = Prefs.isFirewallEnabled(mContext, mPrefs) && Prefs.isFirewallWhitelistMode(mPrefs);
|
||||
|
||||
sent_rcvd.setText(mContext.getString(R.string.rcvd_and_sent, Utils.formatBytes(stats.rcvdBytes), Utils.formatBytes(stats.sentBytes)));
|
||||
traffic.setText(Utils.formatBytes(stats.sentBytes + stats.rcvdBytes));
|
||||
blockedFlag.setVisibility(isBlockedApp ? View.VISIBLE : View.GONE);
|
||||
whitelistedFlag.setVisibility(isWhitelistEnabled && isWhitelistedApp ? View.VISIBLE : View.GONE);
|
||||
@ -120,6 +132,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
|
||||
mListener = null;
|
||||
mStats = new ArrayList<>();
|
||||
mFirewallAvailable = Billing.newInstance(context).isFirewallVisible();
|
||||
mSortField = SortField.NAME;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
@ -198,6 +211,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public void setStats(List<AppStats> stats) {
|
||||
Collections.sort(stats, (o1, o2) -> {
|
||||
AppDescriptor a1 = mApps.getAppByUid(o1.getUid(), 0);
|
||||
@ -212,10 +226,31 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
|
||||
if(a2 == null)
|
||||
return 1;
|
||||
|
||||
return a1.compareTo(a2);
|
||||
switch (mSortField) {
|
||||
case TOTAL_BYTES:
|
||||
return -Long.compare(o1.rcvdBytes + o1.sentBytes,
|
||||
o2.rcvdBytes + o2.sentBytes);
|
||||
case BYTES_SENT:
|
||||
return -Long.compare(o1.sentBytes, o2.sentBytes);
|
||||
case BYTES_RCVD:
|
||||
return -Long.compare(o1.rcvdBytes, o2.rcvdBytes);
|
||||
case NAME:
|
||||
default:
|
||||
return a1.compareTo(a2);
|
||||
}
|
||||
});
|
||||
|
||||
mStats = stats;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public SortField getSortField() {
|
||||
return mSortField;
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public void setSortField(SortField field) {
|
||||
mSortField = field;
|
||||
setStats(mStats);
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,6 +48,7 @@ import com.emanuelef.remote_capture.PCAPdroid;
|
||||
import com.emanuelef.remote_capture.R;
|
||||
import com.emanuelef.remote_capture.activities.AppDetailsActivity;
|
||||
import com.emanuelef.remote_capture.adapters.AppsStatsAdapter;
|
||||
import com.emanuelef.remote_capture.adapters.AppsStatsAdapter.SortField;
|
||||
import com.emanuelef.remote_capture.interfaces.ConnectionsListener;
|
||||
import com.emanuelef.remote_capture.model.AppStats;
|
||||
import com.emanuelef.remote_capture.model.Blocklist;
|
||||
@ -63,6 +64,7 @@ public class AppsFragment extends Fragment implements ConnectionsListener, MenuP
|
||||
private Handler mHandler;
|
||||
private boolean mRefreshApps;
|
||||
private boolean listenerSet;
|
||||
private Menu mMenu;
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
@ -122,14 +124,46 @@ public class AppsFragment extends Fragment implements ConnectionsListener, MenuP
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshSortField() {
|
||||
if((mMenu == null) || (mAdapter == null))
|
||||
return;
|
||||
|
||||
SortField sortField = mAdapter.getSortField();
|
||||
Log.d(TAG, "Sort field:" + sortField);
|
||||
|
||||
MenuItem byName = mMenu.findItem(R.id.sort_by_name);
|
||||
MenuItem byTotalBytes = mMenu.findItem(R.id.sort_by_total_bytes);
|
||||
MenuItem byBytesSent = mMenu.findItem(R.id.sort_by_bytes_sent);
|
||||
MenuItem byBytesRcvd = mMenu.findItem(R.id.sort_by_bytes_rcvd);
|
||||
|
||||
// important: the checked item must first be unchecked
|
||||
byName.setChecked(false);
|
||||
byTotalBytes.setChecked(false);
|
||||
byBytesSent.setChecked(false);
|
||||
byBytesRcvd.setChecked(false);
|
||||
|
||||
if(sortField.equals(SortField.NAME))
|
||||
byName.setChecked(true);
|
||||
else if(sortField.equals(SortField.TOTAL_BYTES))
|
||||
byTotalBytes.setChecked(true);
|
||||
else if(sortField.equals(SortField.BYTES_SENT))
|
||||
byBytesSent.setChecked(true);
|
||||
else if(sortField.equals(SortField.BYTES_RCVD))
|
||||
byBytesRcvd.setChecked(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateMenu(@NonNull Menu menu, MenuInflater menuInflater) {
|
||||
menuInflater.inflate(R.menu.apps_menu, menu);
|
||||
mMenu = menu;
|
||||
refreshSortField();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
|
||||
if(menuItem.getItemId() == R.id.reset) {
|
||||
int id = menuItem.getItemId();
|
||||
|
||||
if(id == R.id.reset) {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setMessage(R.string.reset_stats_confirm)
|
||||
.setPositiveButton(R.string.yes, (dialog, whichButton) -> {
|
||||
@ -143,7 +177,24 @@ public class AppsFragment extends Fragment implements ConnectionsListener, MenuP
|
||||
.show();
|
||||
|
||||
return true;
|
||||
} else if(id == R.id.sort_by_name) {
|
||||
mAdapter.setSortField(SortField.NAME);
|
||||
refreshSortField();
|
||||
return true;
|
||||
} else if(id == R.id.sort_by_total_bytes) {
|
||||
mAdapter.setSortField(SortField.TOTAL_BYTES);
|
||||
refreshSortField();
|
||||
return true;
|
||||
} else if(id == R.id.sort_by_bytes_sent) {
|
||||
mAdapter.setSortField(SortField.BYTES_SENT);
|
||||
refreshSortField();
|
||||
return true;
|
||||
} else if(id == R.id.sort_by_bytes_rcvd) {
|
||||
mAdapter.setSortField(SortField.BYTES_RCVD);
|
||||
refreshSortField();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
5
app/src/main/res/drawable/ic_sort.xml
Normal file
5
app/src/main/res/drawable/ic_sort.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal" 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,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
|
||||
</vector>
|
||||
@ -12,19 +12,37 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
tools:src="@drawable/ic_apps"
|
||||
tools:tint="?attr/colorAccent"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="4dp" />
|
||||
|
||||
<TextView
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="0.5"
|
||||
android:layout_marginStart="5dp"
|
||||
tools:text="Example app"
|
||||
android:maxLines="2"
|
||||
android:gravity="center_vertical"
|
||||
android:id="@+id/app_info"/>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_info"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Example app" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sent_rcvd"
|
||||
tools:text="@string/rcvd_and_sent"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/blocked"
|
||||
|
||||
@ -3,9 +3,27 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:title="@string/sort_by"
|
||||
android:icon="@drawable/ic_sort"
|
||||
app:showAsAction="ifRoom">
|
||||
<menu>
|
||||
<group android:id="@+id/sort_by" android:checkableBehavior="single">
|
||||
<item android:id="@+id/sort_by_name"
|
||||
android:title="@string/app_name"/>
|
||||
<item android:id="@+id/sort_by_total_bytes"
|
||||
android:title="@string/total_bytes"/>
|
||||
<item android:id="@+id/sort_by_bytes_sent"
|
||||
android:title="@string/bytes_sent"/>
|
||||
<item android:id="@+id/sort_by_bytes_rcvd"
|
||||
android:title="@string/bytes_rcvd"/>
|
||||
</group>
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/reset"
|
||||
android:title="@string/reset"
|
||||
android:icon="@drawable/ic_reset"
|
||||
app:showAsAction="ifRoom" />
|
||||
app:showAsAction="ifRoom"/>
|
||||
</menu>
|
||||
@ -451,4 +451,6 @@
|
||||
<string name="many_rules_warning">You are trying to import many rules, which could make the app unresponsive during some interactions. Do you really want to continue?</string>
|
||||
<string name="pcapng_format">PCAPNG format</string>
|
||||
<string name="pcapng_format_summary">Dump packets in the PCAPNG dump format, which allows embedding TLS decryption secrets</string>
|
||||
<string name="sort_by">Sort by</string>
|
||||
<string name="total_bytes">Total bytes</string>
|
||||
</resources>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user