Add app context menu to quickly add firewall rule

Moreover, the apps list now shows a ban icon if the app is blocked
This commit is contained in:
emanuele-f 2022-05-19 14:46:25 +02:00
parent 67dae23ca2
commit 3be18fb2e5
6 changed files with 113 additions and 9 deletions

View File

@ -20,6 +20,7 @@
package com.emanuelef.remote_capture.adapters;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
@ -29,29 +30,37 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import com.emanuelef.remote_capture.CaptureService;
import com.emanuelef.remote_capture.PCAPdroid;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.Utils;
import com.emanuelef.remote_capture.model.AppDescriptor;
import com.emanuelef.remote_capture.model.AppStats;
import com.emanuelef.remote_capture.AppsResolver;
import com.emanuelef.remote_capture.model.MatchList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.ViewHolder> {
private static final String TAG = "ConnectionsAdapter";
private static final String TAG = "AppsStatsAdapter";
private final Context mContext;
private final LayoutInflater mLayoutInflater;
private final Drawable mUnknownIcon;
private final MatchList mBlocklist;
private View.OnClickListener mListener;
private List<AppStats> mStats;
private final AppsResolver mApps;
private final SharedPreferences mPrefs;
private int mClickedPosition;
public static class ViewHolder extends RecyclerView.ViewHolder {
public class ViewHolder extends RecyclerView.ViewHolder {
ImageView icon;
ImageView blockedFlag;
TextView info;
TextView traffic;
@ -59,27 +68,29 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
super(itemView);
icon = itemView.findViewById(R.id.icon);
blockedFlag = itemView.findViewById(R.id.blocked);
info = itemView.findViewById(R.id.app_info);
traffic = itemView.findViewById(R.id.traffic);
}
public void bindAppStats(Context context, AppStats stats, AppsResolver apps, Drawable unknownIcon) {
public void bindAppStats(AppStats stats) {
Drawable appIcon;
// NOTE: can be null
AppDescriptor app = (apps != null) ? apps.get(stats.getUid(), 0) : null;
AppDescriptor app = (mApps != null) ? mApps.get(stats.getUid(), 0) : null;
appIcon = ((app != null) && (app.getIcon() != null)) ? app.getIcon() : unknownIcon;
appIcon = ((app != null) && (app.getIcon() != null)) ? app.getIcon() : mUnknownIcon;
icon.setImageDrawable(appIcon);
String info_txt = (app != null) ? app.getName() : Integer.toString(stats.getUid());
if(stats.numConnections > 1)
info_txt += " (" + Utils.formatNumber(context, stats.numConnections) + ")";
info_txt += " (" + Utils.formatNumber(mContext, stats.numConnections) + ")";
info.setText(info_txt);
traffic.setText(Utils.formatBytes(stats.sentBytes + stats.rcvdBytes));
blockedFlag.setVisibility(mBlocklist.matchesApp(stats.getUid()) ? View.VISIBLE : View.INVISIBLE);
}
}
@ -88,8 +99,10 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
mApps = new AppsResolver(context);
mLayoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mUnknownIcon = ContextCompat.getDrawable(mContext, android.R.drawable.ic_menu_help);
mBlocklist = PCAPdroid.getInstance().getBlocklist();
mListener = null;
mStats = new ArrayList<>();
mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
setHasStableIds(true);
}
@ -106,7 +119,21 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
if(mListener != null)
view.setOnClickListener(mListener);
return new ViewHolder(view);
ViewHolder holder = new ViewHolder(view);
if(CaptureService.isFirewallEnabled(mContext, mPrefs)) {
// Enable the ability to show the context menu
view.setLongClickable(true);
view.setOnLongClickListener(v -> {
// see registerForContextMenu
mClickedPosition = holder.getAbsoluteAdapterPosition();
return false;
});
} else
view.setLongClickable(false);
return holder;
}
@Override
@ -116,7 +143,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
if(stats == null)
return;
holder.bindAppStats(mContext, stats, mApps, mUnknownIcon);
holder.bindAppStats(stats);
}
@Override
@ -130,6 +157,10 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
return mStats.get(pos);
}
public int getClickedItemPos() {
return mClickedPosition;
}
public String getItemPackage(int pos) {
AppStats stats = getItem(pos);

View File

@ -199,6 +199,7 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
ViewHolder holder = new ViewHolder(view);
view.setOnLongClickListener(v -> {
// see registerForContextMenu
mClickedPosition = holder.getAbsoluteAdapterPosition();
return false;
});

View File

@ -26,7 +26,11 @@ import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@ -36,13 +40,17 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.emanuelef.remote_capture.Billing;
import com.emanuelef.remote_capture.CaptureService;
import com.emanuelef.remote_capture.ConnectionsRegister;
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.interfaces.ConnectionsListener;
import com.emanuelef.remote_capture.model.AppStats;
import com.emanuelef.remote_capture.model.ConnectionDescriptor;
import com.emanuelef.remote_capture.model.MatchList;
import com.emanuelef.remote_capture.views.EmptyRecyclerView;
public class AppsFragment extends Fragment implements ConnectionsListener {
@ -78,6 +86,7 @@ public class AppsFragment extends Fragment implements ConnectionsListener {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
mRecyclerView = view.findViewById(R.id.apps_stats_view);
mRecyclerView.setLayoutManager(new EmptyRecyclerView.MyLinearLayoutManager(getContext()));
registerForContextMenu(mRecyclerView);
mAdapter = new AppsStatsAdapter(getContext());
doRefreshApps();
@ -129,6 +138,47 @@ public class AppsFragment extends Fragment implements ConnectionsListener {
}
}
@Override
public void onCreateContextMenu(@NonNull ContextMenu menu, @NonNull View v,
@Nullable ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
Log.d(TAG, "onCreateContextMenu");
MenuInflater inflater = requireActivity().getMenuInflater();
inflater.inflate(R.menu.app_context_menu, menu);
AppStats stats = mAdapter.getItem(mAdapter.getClickedItemPos());
if(stats == null)
return;
// TODO unblock
menu.findItem(R.id.block_app).setVisible(true);
menu.findItem(R.id.unblock_app).setVisible(false);
}
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
MatchList blocklist = PCAPdroid.getInstance().getBlocklist();
int itemPos = mAdapter.getClickedItemPos();
AppStats app = mAdapter.getItem(itemPos);
if(id == R.id.block_app)
blocklist.addApp(app.getUid());
else
return super.onContextItemSelected(item);
// refresh the blocklist
blocklist.save();
if(CaptureService.isServiceActive())
CaptureService.requireInstance().reloadBlocklist();
// refresh the item
mAdapter.notifyItemChanged(itemPos);
return true;
}
private void registerConnsListener() {
if (!listenerSet) {
ConnectionsRegister reg = CaptureService.getConnsRegister();

View File

@ -6,6 +6,7 @@
android:orientation="horizontal"
android:paddingHorizontal="4dp"
android:paddingVertical="2dp"
android:gravity="center_vertical"
android:layout_height="wrap_content">
<ImageView
@ -22,9 +23,15 @@
tools:text="Example app"
android:maxLines="2"
android:gravity="center_vertical"
android:layout_marginEnd="8dp"
android:id="@+id/app_info"/>
<ImageView
android:id="@+id/blocked"
android:src="@drawable/ic_block"
android:layout_marginHorizontal="4dp"
android:layout_width="wrap_content"
android:layout_height="12sp" />
<TextView
android:layout_width="8sp"
android:layout_height="fill_parent"

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/block_app"
android:title="@string/action_block" />
<item
android:id="@+id/unblock_app"
android:title="@string/action_unblock" />
</menu>

View File

@ -349,4 +349,6 @@
<string name="firewall_is_disabled">Firewall is disabled</string>
<string name="firewall_is_enabled">Firewall is enabled</string>
<string name="app_info">App info</string>
<string name="action_block">Block</string>
<string name="action_unblock">Unblock</string>
</resources>