We often do network debugging when developing our own Android projects. We can make a small packet capture tool to help us test network data and deepen our understanding of the network data transmission process. The following is one written in Android Studio The implementation process of a simple Android network capture tool.

First, create a new Android project in Android Studio. You follow the normal steps to create it, choosing an appropriate project name, package name, and other details for your application when creating your project. Make sure you choose the latest Android SDK version, and choose the Empty Activity template.

Step 1: Add dependencies

In the project's build.gradle file, we need to add the following dependencies:

dependencies {
    implementation 'org.pcap4j:pcap4j-core:1.9.1'
    implementation 'org.pcap4j:pcap4j-packetfactory-static:1.9.1'
}

These dependencies add the Pcap4J library we need to use to our project.

Step 2: Create the UI

Create a simple layout file for your application to display captured network packets. You can create a new layout file under the res/layout/ directory.

In MainActivity, we will create a RecyclerView to display the captured packets. We will also add a Clear button for clearing the packet list. Here is the code for our activity_main.xml sample layout file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/recyclerView"
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="1"
         android:scrollbars="vertical" />
     <Button
         android:id="@+id/btnClear"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_horizontal"
         android:text="Clear" />
</LinearLayout>

Step 3: Create a Packet Model

In order to display packets in RecyclerView, we need to create a packet model. We will create a PacketModel class that contains various properties of the packet such as timestamp, source IP address, destination IP address, source port, destination port, and data size.

public class PacketModel {
    private String timestamp;
    private String srcIp;
    private String dstIp;
    private int srcPort;
    private int dstPort;
    private int length;
    public PacketModel(String timestamp, String srcIp, String dstIp, int srcPort, int dstPort, int length) {
        this.timestamp = timestamp;
        this.srcIp = srcIp;
        this.dstIp = dstIp;
        this.srcPort = srcPort;
        this.dstPort = dstPort;
        this.length = length;
    }
    public String getTimestamp() {
        return timestamp;
    }
    public String getSrcIp() {
        return srcIp;
    }
    public String getDstIp() {
        return dstIp;
    }
    public int getSrcPort() {
        return srcPort;
    }
    public int getDstPort() {
        return dstPort;
    }
    public int getLength() {
        return length;
    }
}

Step 4: Create RecyclerView Adapter

Next, we need to create a RecyclerView adapter to manage the list of packets. We will create a PacketAdapter class that extends the RecyclerView.Adapter class and override the necessary methods.

public class PacketAdapter extends RecyclerView.Adapter {
    private List mPackets;
    public PacketAdapter(List packets) {
        mPackets = packets;
    }
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.packet_item, parent, false);
        return new ViewHolder(view);
    }
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        PacketModel packet = mPackets.get(position);
        holder.timestampTextView.setText(packet.getTimestamp());
        holder.srcIpTextView.setText(packet.getSrcIp());
        holder.dstIpTextView.setText(packet.getDstIp());
        holder.srcPortTextView.setText(String.valueOf(packet.getSrcPort()));
        holder.dstPortTextView.setText(String.valueOf(packet.getDstPort()));
        holder.lengthTextView.setText(String.valueOf(packet.getLength()));
    }
    @Override
    public int getItemCount() {
        return mPackets.size();
    }
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView timestampTextView;
        public TextView srcIpTextView;
        public TextView dstIpTextView;
        public TextView srcPortTextView;
        public TextView dstPortTextView;
        public TextView lengthTextView;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            timestampTextView = itemView.findViewById(R.id.timestampTextView);
            srcIpTextView = itemView.findViewById(R.id.srcIpTextView);
            dstIpTextView = itemView.findViewById(R.id.dstIpTextView);
            srcPortTextView = itemView.findViewById(R.id.srcPortTextView);
            dstPortTextView = itemView.findViewById(R.id.dstPortTextView);
            lengthTextView = itemView.findViewById(R.id.lengthTextView);
        }
    }
}

Step 5: Create a packet capture method

Now, we need to write code to capture network packets. We'll create a method in MainActivity to do this. Here is the code for our MainActivity.java file:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();
    private static final int SNAPLEN = 65536;
    private static final int READ_TIMEOUT = 10;
    private static final int BUFFER_SIZE = 1024 * 1024;
    private RecyclerView mRecyclerView;
    private Button mBtnClear;
    private List mPackets = new ArrayList<>();
    private PacketAdapter mAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecyclerView = findViewById(R.id.recyclerView);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mAdapter = new PacketAdapter(mPackets);
        mRecyclerView.setAdapter(mAdapter);
        mBtnClear = findViewById(R.id.btnClear);
        mBtnClear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPackets.clear();
                mAdapter.notifyDataSetChanged();
            }
        });
        startCapture();
    }
    private void startCapture() {
        try {
            // get network interface
            List allDevs = Pcaps.findAllDevs();
            if (allDevs.isEmpty()) {
                Log.e(TAG, "No network interface found.");
                return;
            }
            // select network interface
            PcapNetworkInterface nif = allDevs.get(0);
            // open network interface
            final PacketCapture packetCapture = nif.openLive(SNAPLEN, PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
            // Start capturing packets
            packetCapture.loop(-1, new PacketListener() {
                @Override
                public void gotPacket(Packet packet) {
                    // parse packet
                    byte[] data = packet.getPayload().getRawData();
                    if (data != null && data.length > 0) {
                        String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(new Date(packet.getCaptureHeader().timestampInMillis()));
                        String srcIp = packet.getHeader(IpV4Packet.class).getHeader().getSrcAddr().getHostAddress();
                        String dstIp = packet.getHeader(IpV4Packet.class).getHeader().getDstAddr().getHostAddress();
                        int srcPort = packet.getHeader(TcpPacket.class).getHeader().getSrcPort().valueAsInt();
                        int dstPort = packet.getHeader(TcpPacket.class).getHeader().getDstPort().valueAsInt();
                        int length = data.length;
                        PacketModel packetModel = new PacketModel(timestamp, srcIp, dstIp, srcPort, dstPort, length);
                        mPackets.add(packetModel);
                        // Update the UI
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                mAdapter.notifyItemInserted(mPackets.size() - 1);
                                mRecyclerView.smoothScrollToPosition(mPackets.size() - 1);
                            }
                        });
                    }
                }
            });
        } catch (PcapNativeException e) {
            Log.e(TAG, "Error while opening device: " + e.getMessage());
        } catch (InterruptedException e) {
            Log.e(TAG, "Error while capturing packets: " + e.getMessage());
        }
    }
}

We start by getting the network interfaces and then select the first one. Next, we open the interface and use PacketListener to capture packets. For each captured packet, we parse it and add it to the mPackets list. Finally, we notify the adapter in the UI thread so that the UI list is updated and scrolled to the last entry.

In the onCreate() method, we set up the RecyclerView and adapter, and add a click listener for the clear button. Then, we call the startCapture() method to start capturing packets.

Finally, we need to declare Internet permission in the manifest file so we can access the network:

<uses-permission android:name="android.permission.INTERNET" />

Now, we can compile and run the application. After running the app in the emulator or on an actual device, you should see the list start to populate with packets. If you open your device's browser and browse some web pages, you should be able to see the HTTP requests and responses being fetched.

This is a simple packet capture application example, you can add more features and improvements based on it, such as filters, more advanced parsing, exporting functions and so on.

The full code is as follows:

public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private PacketAdapter adapter;
    private List packetList = new ArrayList<>();
    private Button clearButton;
    private PacketCapture packetCapture;
    private static final int SNAP_LEN = 65536;
    private static final int READ_TIMEOUT = 1000;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerView = findViewById(R.id.recyclerView);
        adapter = new PacketAdapter(packetList);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(adapter);
        clearButton = findViewById(R.id.clearButton);
        clearButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                packetList.clear();
                adapter.notifyDataSetChanged();
            }
        });
        startCapture();
    }
    private void startCapture() {
        try {
            NetworkInterface nif = PcapNetworkInterface.getSelectedInterface();
            packetCapture = nif.openLive(SNAP_LEN, PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!Thread.currentThread().isInterrupted()) {
                        Packet packet = packetCapture.getNextPacket();
                        if (packet != null) {
                            packetList.add(packet);
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    adapter.notifyItemInserted(packetList.size() - 1);
                                    recyclerView.scrollToPosition(packetList.size() - 1);
                                }
                            });
                        }
                    }
                }
            }).start();
        } catch (PcapNativeException e) {
            e.printStackTrace();
        } catch (NotOpenException e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (packetCapture != null) {
            packetCapture.breakLoop();
            packetCapture.close();
        }
    }
}

Here is the Packet class:

public class Packet {
    private String time;
    private String sourceIp;
    private String destinationIp;
    private String protocol;
    private String length;
    public Packet(String time, String sourceIp, String destinationIp, String protocol, String length) {
        this.time = time;
        this.sourceIp = sourceIp;
        this.destinationIp = destinationIp;
        this.protocol = protocol;
        this.length = length;
    }
    public String getTime() {
        return time;
    }
    public String getSourceIp() {
        return sourceIp;
    }
    public String getDestinationIp() {
        return destinationIp;
    }
    public String getProtocol() {
        return protocol;
    }
    public String getLength() {
        return length;
    }
}

Here is the PacketAdapter class:

public class PacketAdapter extends RecyclerView.Adapter {
    private List packetList;
    public PacketAdapter(List packetList) {
        this.packetList = packetList;
    }
    @NonNull
    @Override
    public PacketViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.packet_item, parent, false);
        return new PacketViewHolder(view);
    }
    @Override
    public void onBindViewHolder(@NonNull PacketViewHolder holder, int position) {
        Packet packet = packetList.get(position);
        holder.timeTextView.setText(packet.getTime());
        holder.sourceIpTextView.setText(packet.getSourceIp());
        holder.destinationIpTextView.setText(packet.getDestinationIp());
        holder.protocolTextView.setText(packet.getProtocol());
        holder.lengthTextView.setText(packet.getLength());
    }
    @Override
    public int getItemCount() {
        return packetList.size();
    }

 static class PacketViewHolder extends RecyclerView.ViewHolder {
        private TextView timeTextView;
        private TextView sourceIpTextView;
        private TextView destinationIpTextView;
        private TextView protocolTextView;
        private TextView lengthTextView;
        public PacketViewHolder(@NonNull View itemView) {
            super(itemView);
            timeTextView = itemView.findViewById(R.id.timeTextView);
            sourceIpTextView = itemView.findViewById(R.id.sourceIpTextView);
            destinationIpTextView = itemView.findViewById(R.id.destinationIpTextView);
            protocolTextView = itemView.findViewById(R.id.protocolTextView);
            lengthTextView = itemView.findViewById(R.id.lengthTextView);
        }
    }
}


Finally, you also need to add network permissions to the AndroidManifest.xml file:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

In this way, we have completed a simple network capture application. When we run the program, it will start capturing all the packets passing over the phone's network interface and display them in a RecyclerView list. We've also added a clear button to empty the list if needed.

Note that this is a simple sample application for educational purposes only. In practice, you may want to perform a more detailed analysis of network packets and tailor them to your specific needs. Also, be sure to comply with all relevant laws and regulations when using web capture applications to avoid issues such as privacy and network security.

Hope this article helps you!