Add HTTP scroll to request/reply button

This commit is contained in:
emanuele-f 2022-04-11 12:58:28 +02:00
parent 64721ea64c
commit 00c4d08deb
4 changed files with 98 additions and 11 deletions

View File

@ -20,6 +20,7 @@
package com.emanuelef.remote_capture.adapters;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -28,6 +29,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.emanuelef.remote_capture.CaptureService;
@ -38,6 +40,7 @@ import com.emanuelef.remote_capture.model.ConnectionDescriptor;
import com.emanuelef.remote_capture.model.PayloadChunk;
import com.emanuelef.remote_capture.model.PayloadChunk.ChunkType;
import com.emanuelef.remote_capture.model.Prefs;
import com.google.android.material.button.MaterialButton;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
@ -50,6 +53,7 @@ import java.util.Locale;
* Since the text of a chunk can be very long (hundreds of KB) and rendering it would freeze the UI,
* it is split into pages of VISUAL_PAGE_SIZE. */
public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadViewHolder> implements HTTPReassembly.ReassemblyListener {
private static final String TAG = "PayloadAdapter";
public static final int COLLAPSE_CHUNK_SIZE = 1500;
public static final int VISUAL_PAGE_SIZE = 4020; // must be a multiple of 67 to avoid splitting the hexdump
private final LayoutInflater mLayoutInflater;
@ -57,12 +61,15 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
private final Context mContext;
private final ChunkType mMode;
private int mHandledChunks;
private AdapterChunk mUnmatchedHttpReq = null;
private final ArrayList<AdapterChunk> mChunks = new ArrayList<>();
private final LinearLayoutManager mLinearLayout;
private final HTTPReassembly mHttpReq;
private final HTTPReassembly mHttpRes;
public PayloadAdapter(Context context, ConnectionDescriptor conn, ChunkType mode) {
public PayloadAdapter(Context context, LinearLayoutManager linearLayout, ConnectionDescriptor conn, ChunkType mode) {
mLayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mLinearLayout = linearLayout;
mConn = conn;
mContext = context;
mMode = mode;
@ -83,6 +90,7 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
private boolean mIsExpanded;
private int mNumPages = 1;
public final int originalPos;
public AdapterChunk peer = null;
AdapterChunk(PayloadChunk _chunk, int pos) {
mChunk = _chunk;
@ -186,6 +194,7 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
TextView header;
TextView dump;
ImageView expandButton;
MaterialButton jumpToReply;
public PayloadViewHolder(View view) {
super(view);
@ -193,6 +202,7 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
header = view.findViewById(R.id.header);
dump = view.findViewById(R.id.dump);
expandButton = view.findViewById(R.id.expand_button);
jumpToReply = view.findViewById(R.id.jumpToReply);
}
}
@ -219,6 +229,16 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
}
});
holder.jumpToReply.setOnClickListener(v -> {
int pos = holder.getAbsoluteAdapterPosition();
Page page = getItem(pos);
assert(page.adaptChunk.peer != null);
int jumpPos = getAdapterPosition(page.adaptChunk.peer);
Log.d(TAG, "jump to " + jumpPos + " (orig_pos=" + page.adaptChunk.peer.originalPos + ")");
mLinearLayout.scrollToPosition(jumpPos);
});
return holder;
}
@ -252,6 +272,8 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
} else
holder.expandButton.setVisibility(View.GONE);
holder.jumpToReply.setVisibility((page.adaptChunk.peer != null) ? View.VISIBLE : View.GONE);
holder.dump.setText(page.getText());
if(chunk.is_sent) {
@ -298,6 +320,21 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
return mChunks.get(i).getPage(pageIdx);
}
private int getAdapterPosition(AdapterChunk chunk) {
int i;
int count = 0;
for(i=0; i < mChunks.size(); i++) {
AdapterChunk aChunk = mChunks.get(i);
if(aChunk == chunk)
break;
count += aChunk.getNumPages();
}
return count;
}
public void handleChunksAdded(int tot_chunks) {
for(int i = mHandledChunks; i<tot_chunks; i++) {
PayloadChunk chunk = mConn.getPayloadChunk(i);
@ -324,8 +361,29 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
@Override
public void onChunkReassembled(PayloadChunk chunk) {
AdapterChunk adapterChunk = new AdapterChunk(chunk, mChunks.size());
if(!chunk.is_sent && (mUnmatchedHttpReq != null)) {
// HTTP reply
adapterChunk.peer = mUnmatchedHttpReq;
mUnmatchedHttpReq.peer = adapterChunk;
notifyItemChanged(getAdapterPosition(mUnmatchedHttpReq));
mUnmatchedHttpReq = null;
// Possibly find next un-replied HTTP request
for(int i=adapterChunk.peer.originalPos + 1; i<mChunks.size(); i++) {
AdapterChunk cur = mChunks.get(i);
if(cur.mChunk.is_sent) {
mUnmatchedHttpReq = cur;
break;
}
}
} else if((chunk.is_sent) && (mUnmatchedHttpReq == null))
mUnmatchedHttpReq = adapterChunk;
int insert_pos = getItemCount();
mChunks.add(new AdapterChunk(chunk, mChunks.size()));
mChunks.add(adapterChunk);
notifyItemInserted(insert_pos);
}
}

View File

@ -91,7 +91,7 @@ public class ConnectionPayload extends Fragment implements ConnectionDetailsActi
if(mConn.isPayloadTruncated())
mTruncatedWarning.setVisibility(View.VISIBLE);
mAdapter = new PayloadAdapter(requireContext(), mConn, mode);
mAdapter = new PayloadAdapter(requireContext(), layoutMan, mConn, mode);
mCurChunks = mConn.getNumPayloadChunks();
recyclerView.setAdapter(mAdapter);
}

View File

@ -0,0 +1,5 @@
<vector android:height="18dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="18dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M11,9l1.42,1.42L8.83,14H18V4h2v12H8.83l3.59,3.58L11,21l-6,-6 6,-6z"/>
</vector>

View File

@ -3,19 +3,43 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:paddingHorizontal="3sp"
android:paddingVertical="10dp">
android:paddingVertical="8dp">
<TextView
android:id="@+id/header"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:textSize="11sp"
android:layout_marginBottom="12dp"
android:textStyle="bold"
tools:text="#1 [TX] 11:02:03.154 — 120 B" />
android:gravity="center_vertical"
android:layout_marginBottom="10dp"
android:orientation="horizontal">
<TextView
android:id="@+id/header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="monospace"
android:textSize="11sp"
android:textStyle="bold"
tools:text="#1 [TX] 11:02:03.154 — 120 B" />
<com.google.android.material.button.MaterialButton
android:id="@+id/jumpToReply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:insetTop="0dp"
android:insetBottom="0dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:paddingVertical="0dp"
android:paddingHorizontal="0dp"
android:padding="4dp"
app:icon="@drawable/ic_subdirectory_arrow_left"
app:iconTint="@color/colorTabText" />
</LinearLayout>
<TextView
android:id="@+id/dump"