Migrate connections view to RecyclerView

This commit is contained in:
emanuele-f 2021-02-12 18:25:54 +01:00
parent c43f2e8f6c
commit 9722b221ce
6 changed files with 162 additions and 71 deletions

View File

@ -44,7 +44,8 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.3.0'
// Third-party
implementation 'cat.ereza:customactivityoncrash:2.3.0'

View File

@ -21,81 +21,99 @@ package com.emanuelef.remote_capture;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.telecom.ConnectionRequest;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Objects;
public class ConnectionsAdapter extends BaseAdapter {
private static final String TAG = "ConnectionsAdapter";
private final MainActivity mActivity;
private final Drawable mUnknownIcon;
class ViewHolder extends RecyclerView.ViewHolder {
ImageView icon;
TextView remote;
TextView l7proto;
TextView traffic;
ConnectionsAdapter(MainActivity context) {
mActivity = context;
mUnknownIcon = ContextCompat.getDrawable(mActivity, android.R.drawable.ic_menu_help);
ViewHolder(View itemView) {
super(itemView);
icon = itemView.findViewById(R.id.icon);
remote = itemView.findViewById(R.id.remote);
l7proto = itemView.findViewById(R.id.l7proto);
traffic = itemView.findViewById(R.id.traffic);
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
ConnDescriptor conn = getItem(position);
if(convertView == null) {
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
assert inflater != null;
convertView = inflater.inflate(R.layout.connection_item, parent, false);
}
assert conn != null;
ImageView icon = convertView.findViewById(R.id.icon);
TextView remote = convertView.findViewById(R.id.remote);
TextView l7proto = convertView.findViewById(R.id.l7proto);
TextView traffic = convertView.findViewById(R.id.traffic);
AppDescriptor app = mActivity.findAppByUid(conn.uid);
public void bindConn(MainActivity activity, ConnDescriptor conn, Drawable unknownIcon) {
AppDescriptor app = activity.findAppByUid(conn.uid);
Drawable appIcon;
appIcon = (app != null) ? Objects.requireNonNull(app.getIcon().getConstantState()).newDrawable() : mUnknownIcon;
appIcon = (app != null) ? Objects.requireNonNull(app.getIcon().getConstantState()).newDrawable() : unknownIcon;
icon.setImageDrawable(appIcon);
if(conn.info.length() > 0)
remote.setText(conn.info);
else
remote.setText(String.format(mActivity.getResources().getString(R.string.ip_and_port),
remote.setText(String.format(activity.getResources().getString(R.string.ip_and_port),
conn.dst_ip, conn.dst_port));
l7proto.setText(conn.l7proto);
traffic.setText(Utils.formatBytes(conn.sent_bytes + conn.rcvd_bytes));
}
}
return(convertView);
public class ConnectionsAdapter extends RecyclerView.Adapter<ViewHolder> {
private static final String TAG = "ConnectionsAdapter";
private final MainActivity mActivity;
private final Drawable mUnknownIcon;
private View.OnClickListener mListener;
ConnectionsAdapter(MainActivity context) {
mActivity = context;
mUnknownIcon = ContextCompat.getDrawable(mActivity, android.R.drawable.ic_menu_help);
mListener = null;
}
@Override
public int getCount() {
public int getItemCount() {
ConnectionsRegister reg = CaptureService.getConnsRegister();
return (reg != null) ? reg.getConnCount() : 0;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
assert inflater != null;
View view = inflater.inflate(R.layout.connection_item, parent, false);
if(mListener != null)
view.setOnClickListener(mListener);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ConnDescriptor conn = getItem(position);
if(conn == null)
return;
holder.bindConn(mActivity, conn, mUnknownIcon);
}
@Override
public long getItemId(int pos) {
ConnectionsRegister reg = CaptureService.getConnsRegister();
if((pos < 0) || (pos >= getCount()) || (reg == null))
if((pos < 0) || (pos >= getItemCount()) || (reg == null))
return -1;
ConnDescriptor conn = reg.getConn(pos);
@ -103,14 +121,17 @@ public class ConnectionsAdapter extends BaseAdapter {
return ((conn != null) ? conn.incr_id : -1);
}
@Override
public ConnDescriptor getItem(int pos) {
ConnectionsRegister reg = CaptureService.getConnsRegister();
/* Prevent indexOutOfBounds exception in updateView() */
if((pos < 0) || (pos >= getCount()) || (reg == null))
if((pos < 0) || (pos >= getItemCount()) || (reg == null))
return null;
return reg.getConn(pos);
}
public void setClickListener(View.OnClickListener listener) {
mListener = listener;
}
}

View File

@ -26,13 +26,12 @@ import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
public class ConnectionsFragment extends Fragment implements AppStateListener, ConnectionsListener {
private static final String TAG = "ConnectionsFragment";
@ -67,36 +66,37 @@ public class ConnectionsFragment extends Fragment implements AppStateListener, C
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
ListView connList = view.findViewById(R.id.connections_view);
EmptyRecyclerView connList = view.findViewById(R.id.connections_view);
connList.setLayoutManager(new LinearLayoutManager(mActivity));
TextView emptyText = view.findViewById(R.id.no_connections);
connList.setEmptyView(emptyText);
mAdapter = new ConnectionsAdapter(mActivity);
connList.setAdapter(mAdapter);
connList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int pos, long id) {
ConnDescriptor item = (ConnDescriptor) adapterView.getItemAtPosition(pos);
if(item != null) {
Intent intent = new Intent(getContext(), ConnectionDetails.class);
AppDescriptor app = mActivity.findAppByUid(item.uid);
String app_name = null;//;1051
mAdapter.setClickListener(v -> {
int pos = connList.getChildLayoutPosition(v);
ConnDescriptor item = mAdapter.getItem(pos);
if(app != null)
app_name = app.getName();
else if(item.uid == 1000)
app_name = "system";
else if(item.uid == 1051)
app_name = "netd";
if(item != null) {
Intent intent = new Intent(getContext(), ConnectionDetails.class);
AppDescriptor app = mActivity.findAppByUid(item.uid);
String app_name = null;//;1051
intent.putExtra(ConnectionDetails.CONN_EXTRA_KEY, item);
if(app != null)
app_name = app.getName();
else if(item.uid == 1000)
app_name = "system";
else if(item.uid == 1051)
app_name = "netd";
if(app_name != null)
intent.putExtra(ConnectionDetails.APP_NAME_EXTRA_KEY, app_name);
intent.putExtra(ConnectionDetails.CONN_EXTRA_KEY, item);
startActivity(intent);
}
if(app_name != null)
intent.putExtra(ConnectionDetails.APP_NAME_EXTRA_KEY, app_name);
startActivity(intent);
}
});
@ -127,17 +127,12 @@ public class ConnectionsFragment extends Fragment implements AppStateListener, C
@Override
public void appsLoaded() {
// Refresh the adapter to load the apps
// Refresh the adapter to load the apps icons
mAdapter.notifyDataSetChanged();
}
@Override
public void connectionsChanges() {
mHandler.post(new Runnable() {
@Override
public void run() {
mAdapter.notifyDataSetChanged();
}
});
mHandler.post(() -> mAdapter.notifyDataSetChanged());
}
}

View File

@ -0,0 +1,73 @@
package com.emanuelef.remote_capture;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
// Adapter from https://gist.github.com/AlexZhukovich/537eaa1e3c82ef9f5d5cd22efdc80c54#file-emptyrecyclerview-java
public class EmptyRecyclerView extends RecyclerView {
private View mEmptyView;
public EmptyRecyclerView(Context context) {
super(context);
}
public EmptyRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public EmptyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private void initEmptyView() {
if (mEmptyView != null) {
mEmptyView.setVisibility(
getAdapter() == null || getAdapter().getItemCount() == 0 ? VISIBLE : GONE);
EmptyRecyclerView.this.setVisibility(
getAdapter() == null || getAdapter().getItemCount() == 0 ? GONE : VISIBLE);
}
}
final AdapterDataObserver observer = new AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
initEmptyView();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
initEmptyView();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
initEmptyView();
}
};
@Override
public void setAdapter(Adapter adapter) {
Adapter oldAdapter = getAdapter();
super.setAdapter(adapter);
if (oldAdapter != null) {
oldAdapter.unregisterAdapterDataObserver(observer);
}
if (adapter != null) {
adapter.registerAdapterDataObserver(observer);
}
}
public void setEmptyView(View view) {
this.mEmptyView = view;
initEmptyView();
}
}

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="?attr/selectableItemBackground"
android:paddingHorizontal="5dp"
android:layout_height="40dp">

View File

@ -4,11 +4,11 @@
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
<com.emanuelef.remote_capture.EmptyRecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/connections_view"
></ListView>
></com.emanuelef.remote_capture.EmptyRecyclerView>
<TextView
android:id="@+id/no_connections"