mirror of
https://github.com/emanuele-f/PCAPdroid.git
synced 2026-06-16 21:10:57 +08:00
Improve connection overview
- Show the decryption status - Show payload size - Show warning if connection start is not seen (root)
This commit is contained in:
parent
06a98d3486
commit
05ca7a4617
@ -59,6 +59,7 @@ import android.text.SpannableString;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
@ -67,6 +68,7 @@ import android.widget.Toast;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.emanuelef.remote_capture.interfaces.TextAdapter;
|
||||
@ -1083,4 +1085,28 @@ public class Utils {
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static void setDecryptionIcon(ImageView icon, ConnectionDescriptor conn) {
|
||||
int color;
|
||||
|
||||
switch(conn.getDecryptionStatus()) {
|
||||
case DECRYPTED:
|
||||
color = R.color.ok;
|
||||
break;
|
||||
case NOT_DECRYPTABLE:
|
||||
color = R.color.warning;
|
||||
break;
|
||||
case TLS_ERROR:
|
||||
color = R.color.danger;
|
||||
break;
|
||||
default:
|
||||
color = R.color.lightGray;
|
||||
}
|
||||
|
||||
Context context = icon.getContext();
|
||||
int resid = (conn.isCleartext() || conn.isDecrypted()) ? R.drawable.ic_lock_open : R.drawable.ic_lock;
|
||||
|
||||
icon.setColorFilter(ContextCompat.getColor(context, color));
|
||||
icon.setImageDrawable(ContextCompat.getDrawable(context, resid));
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ public class ConnectionDetailsActivity extends BaseActivity implements Connectio
|
||||
case POS_HTTP:
|
||||
return R.string.http;
|
||||
case POS_RAW_PAYLOAD:
|
||||
return R.string.raw_payload;
|
||||
return R.string.payload;
|
||||
case POS_OVERVIEW:
|
||||
default:
|
||||
return R.string.overview;
|
||||
|
||||
@ -84,8 +84,6 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
|
||||
TextView lastSeen;
|
||||
//FlagImageView countryFlag;
|
||||
final String mProtoAndPort;
|
||||
final Drawable lockOpen;
|
||||
final Drawable lockClosed;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
@ -104,9 +102,6 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
|
||||
|
||||
Context context = itemView.getContext();
|
||||
mProtoAndPort = context.getString(R.string.proto_and_port);
|
||||
|
||||
lockOpen = ContextCompat.getDrawable(context, R.drawable.ic_lock_open);
|
||||
lockClosed = ContextCompat.getDrawable(context, R.drawable.ic_lock);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@ -161,25 +156,8 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
|
||||
blockedInd.setVisibility(conn.is_blocked ? View.VISIBLE : View.GONE);
|
||||
|
||||
if(CaptureService.isDecryptingTLS()) {
|
||||
switch(conn.getDecryptionStatus()) {
|
||||
case CLEARTEXT:
|
||||
case DECRYPTION_IN_PROGRESS:
|
||||
color = R.color.lightGray;
|
||||
break;
|
||||
case DECRYPTED:
|
||||
color = R.color.ok;
|
||||
break;
|
||||
case NOT_DECRYPTABLE:
|
||||
color = R.color.warning;
|
||||
break;
|
||||
case TLS_ERROR:
|
||||
color = R.color.danger;
|
||||
break;
|
||||
}
|
||||
|
||||
decryptionInd.setVisibility(View.VISIBLE);
|
||||
decryptionInd.setColorFilter(ContextCompat.getColor(context, color));
|
||||
decryptionInd.setImageDrawable((conn.isCleartext() || conn.isDecrypted()) ? lockOpen : lockClosed);
|
||||
Utils.setDecryptionIcon(decryptionInd, conn);
|
||||
} else
|
||||
decryptionInd.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@ -37,6 +37,7 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.emanuelef.remote_capture.AppsResolver;
|
||||
@ -53,14 +54,17 @@ public class ConnectionOverview extends Fragment implements ConnectionDetailsAct
|
||||
private ConnectionDescriptor mConn;
|
||||
private TableLayout mTable;
|
||||
private TextView mBytesView;
|
||||
private TextView mPayload;
|
||||
private TextView mPacketsView;
|
||||
private TextView mDurationView;
|
||||
private TextView mBlockedPkts;
|
||||
private View mBlockedPktsRow;
|
||||
private TextView mStatus;
|
||||
private TextView mDecStatus;
|
||||
private ImageView mDecIcon;
|
||||
private TextView mFirstSeen;
|
||||
private TextView mLastSeen;
|
||||
private TextView mTcpFlags;
|
||||
//private TextView mTcpFlags;
|
||||
private TextView mError;
|
||||
private ImageView mBlacklistedIp;
|
||||
private ImageView mBlacklistedHost;
|
||||
@ -102,15 +106,18 @@ public class ConnectionOverview extends Fragment implements ConnectionDetailsAct
|
||||
FlagImageView country_flag = view.findViewById(R.id.country_flag);
|
||||
TextView asn = view.findViewById(R.id.asn);
|
||||
mTable = view.findViewById(R.id.table);
|
||||
mPayload = view.findViewById(R.id.detail_payload);
|
||||
mBytesView = view.findViewById(R.id.detail_bytes);
|
||||
mPacketsView = view.findViewById(R.id.detail_packets);
|
||||
mBlockedPkts = view.findViewById(R.id.blocked_pkts);
|
||||
mBlockedPktsRow = view.findViewById(R.id.blocked_row);
|
||||
mDurationView = view.findViewById(R.id.detail_duration);
|
||||
mStatus = view.findViewById(R.id.detail_status);
|
||||
mDecStatus = view.findViewById(R.id.detail_decryption_status);
|
||||
mDecIcon = view.findViewById(R.id.decryption_icon);
|
||||
mFirstSeen = view.findViewById(R.id.first_seen);
|
||||
mLastSeen = view.findViewById(R.id.last_seen);
|
||||
mTcpFlags = view.findViewById(R.id.tcp_flags);
|
||||
//mTcpFlags = view.findViewById(R.id.tcp_flags);
|
||||
mError = view.findViewById(R.id.error_msg);
|
||||
mBlacklistedIp = view.findViewById(R.id.blacklisted_ip);
|
||||
mBlacklistedHost = view.findViewById(R.id.blacklisted_host);
|
||||
@ -123,7 +130,7 @@ public class ConnectionOverview extends Fragment implements ConnectionDetailsAct
|
||||
if(mConn != null) {
|
||||
String l4proto = Utils.proto2str(mConn.ipproto);
|
||||
//if(l4proto.equals("TCP"))
|
||||
// findViewById(R.id.tcp_flags_row).setVisibility(View.VISIBLE);
|
||||
// view.findViewById(R.id.tcp_flags_row).setVisibility(View.VISIBLE);
|
||||
|
||||
if(!mConn.l7proto.equals(l4proto))
|
||||
proto.setText(String.format(getResources().getString(R.string.app_and_proto), mConn.l7proto, l4proto));
|
||||
@ -154,6 +161,8 @@ public class ConnectionOverview extends Fragment implements ConnectionDetailsAct
|
||||
else
|
||||
appLabel.setText(uid_str);
|
||||
|
||||
view.findViewById(R.id.decryption_status_row).setVisibility(CaptureService.isDecryptingTLS() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if(!mConn.url.isEmpty())
|
||||
url.setText(mConn.url);
|
||||
else
|
||||
@ -216,6 +225,9 @@ public class ConnectionOverview extends Fragment implements ConnectionDetailsAct
|
||||
|
||||
@Override
|
||||
public void connectionUpdated() {
|
||||
Context context = mBytesView.getContext();
|
||||
|
||||
mPayload.setText(Utils.formatBytes(mConn.payload_length));
|
||||
mBytesView.setText(String.format(getResources().getString(R.string.rcvd_and_sent), Utils.formatBytes(mConn.rcvd_bytes), Utils.formatBytes(mConn.sent_bytes)));
|
||||
mPacketsView.setText(String.format(getResources().getString(R.string.rcvd_and_sent), Utils.formatIntShort(mConn.rcvd_pkts), Utils.formatIntShort(mConn.sent_pkts)));
|
||||
|
||||
@ -228,13 +240,20 @@ public class ConnectionOverview extends Fragment implements ConnectionDetailsAct
|
||||
mFirstSeen.setText(Utils.formatEpochMillis(mActivity, mConn.first_seen));
|
||||
mLastSeen.setText(Utils.formatEpochMillis(mActivity, mConn.last_seen));
|
||||
mStatus.setText(mConn.getStatusLabel(mActivity));
|
||||
mTcpFlags.setText(Utils.tcpFlagsToStr(mConn.getRcvdTcpFlags()) + " <- " + Utils.tcpFlagsToStr(mConn.getSentTcpFlags()));
|
||||
mDecStatus.setText(mConn.getDecryptionStatusLabel(mActivity));
|
||||
Utils.setDecryptionIcon(mDecIcon, mConn);
|
||||
//mTcpFlags.setText(Utils.tcpFlagsToStr(mConn.getRcvdTcpFlags()) + " <- " + Utils.tcpFlagsToStr(mConn.getSentTcpFlags()));
|
||||
mBlacklistedIp.setVisibility(mConn.isBlacklistedIp() ? View.VISIBLE : View.GONE);
|
||||
mBlacklistedHost.setVisibility(mConn.isBlacklistedHost() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if(mConn.tls_error != null) {
|
||||
mError.setTextColor(ContextCompat.getColor(context, R.color.danger));
|
||||
mError.setText(mConn.tls_error);
|
||||
mError.setVisibility(View.VISIBLE);
|
||||
} else if(!mConn.hasSeenStart()) {
|
||||
mError.setTextColor(ContextCompat.getColor(context, R.color.warning));
|
||||
mError.setText(context.getString(R.string.connection_start_not_seen));
|
||||
mError.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,6 +84,7 @@ public class ConnectionDescriptor {
|
||||
/* Data */
|
||||
public long first_seen;
|
||||
public long last_seen;
|
||||
public long payload_length;
|
||||
public long sent_bytes;
|
||||
public long rcvd_bytes;
|
||||
public int sent_pkts;
|
||||
@ -135,6 +136,7 @@ public class ConnectionDescriptor {
|
||||
public void processUpdate(ConnectionUpdate update) {
|
||||
// The "update_type" is used to limit the amount of data sent via the JNI
|
||||
if((update.update_type & ConnectionUpdate.UPDATE_STATS) != 0) {
|
||||
payload_length = update.payload_length;
|
||||
sent_bytes = update.sent_bytes;
|
||||
rcvd_bytes = update.rcvd_bytes;
|
||||
sent_pkts = update.sent_pkts;
|
||||
@ -145,7 +147,7 @@ public class ConnectionDescriptor {
|
||||
blacklisted_ip = (update.status & 0x0100) != 0;
|
||||
blacklisted_host = (update.status & 0x0200) != 0;
|
||||
last_seen = update.last_seen;
|
||||
tcp_flags = update.tcp_flags;
|
||||
tcp_flags = update.tcp_flags; // NOTE: only for root capture
|
||||
|
||||
// see MitmReceiver.handlePayload
|
||||
if((status == ConnectionDescriptor.CONN_STATUS_CLOSED) && (tls_error != null))
|
||||
@ -239,7 +241,7 @@ public class ConnectionDescriptor {
|
||||
int resid;
|
||||
|
||||
switch (status) {
|
||||
case CLEARTEXT: resid = R.string.cleartext; break;
|
||||
case CLEARTEXT: resid = R.string.not_encrypted; break;
|
||||
case NOT_DECRYPTABLE: resid = R.string.not_decryptable; break;
|
||||
case DECRYPTED: resid = R.string.decrypted; break;
|
||||
case DECRYPTION_IN_PROGRESS: resid = R.string.in_progress; break;
|
||||
@ -328,6 +330,13 @@ public class ConnectionDescriptor {
|
||||
public String getHttpRequest() { return getHttp(true); }
|
||||
public String getHttpResponse() { return getHttp(false); }
|
||||
|
||||
public boolean hasSeenStart() {
|
||||
if((ipproto != 6 /* TCP */) || !CaptureService.isCapturingAsRoot())
|
||||
return true;
|
||||
|
||||
return (getSentTcpFlags() & 0x2) != 0; // SYN
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return "[proto=" + ipproto + "/" + l7proto + "]: " + src_ip + ":" + src_port + " -> " +
|
||||
|
||||
@ -33,6 +33,7 @@ public class ConnectionUpdate {
|
||||
|
||||
/* set if update_type & UPDATE_STATS */
|
||||
public long last_seen;
|
||||
public long payload_length;
|
||||
public long sent_bytes;
|
||||
public long rcvd_bytes;
|
||||
public int sent_pkts;
|
||||
@ -55,12 +56,13 @@ public class ConnectionUpdate {
|
||||
incr_id = _incr_id;
|
||||
}
|
||||
|
||||
public void setStats(long _last_seen, long _sent_bytes, long _rcvd_bytes,
|
||||
public void setStats(long _last_seen, long _payload_length, long _sent_bytes, long _rcvd_bytes,
|
||||
int _sent_pkts, int _rcvd_pkts, int _blocked_pkts,
|
||||
int _tcp_flags, int _status) {
|
||||
update_type |= UPDATE_STATS;
|
||||
|
||||
last_seen = _last_seen;
|
||||
payload_length = _payload_length;
|
||||
sent_bytes = _sent_bytes;
|
||||
rcvd_bytes = _rcvd_bytes;
|
||||
sent_pkts = _sent_pkts;
|
||||
|
||||
@ -151,7 +151,7 @@ static jobject getConnUpdate(pcapdroid_t *pd, const conn_and_tuple_t *conn) {
|
||||
bool blocked = data->to_block && !pd->root_capture; // currently can only block connections in non-root mode
|
||||
|
||||
(*env)->CallVoidMethod(env, update, mids.connUpdateSetStats, data->last_seen,
|
||||
data->sent_bytes, data->rcvd_bytes, data->sent_pkts, data->rcvd_pkts, data->blocked_pkts,
|
||||
data->payload_length, data->sent_bytes, data->rcvd_bytes, data->sent_pkts, data->rcvd_pkts, data->blocked_pkts,
|
||||
(data->tcp_flags[0] << 8) | data->tcp_flags[1],
|
||||
(blocked << 10) | (data->blacklisted_domain << 9) |
|
||||
(data->blacklisted_ip << 8) | (data->status & 0xFF));
|
||||
@ -510,7 +510,7 @@ Java_com_emanuelef_remote_1capture_CaptureService_runPacketLoop(JNIEnv *env, jcl
|
||||
mids.connInit = jniGetMethodID(env, cls.conn, "<init>", "(IIILjava/lang/String;Ljava/lang/String;IIIIIZJ)V");
|
||||
mids.connProcessUpdate = jniGetMethodID(env, cls.conn, "processUpdate", "(Lcom/emanuelef/remote_capture/model/ConnectionUpdate;)V");
|
||||
mids.connUpdateInit = jniGetMethodID(env, cls.conn_update, "<init>", "(I)V");
|
||||
mids.connUpdateSetStats = jniGetMethodID(env, cls.conn_update, "setStats", "(JJJIIIII)V");
|
||||
mids.connUpdateSetStats = jniGetMethodID(env, cls.conn_update, "setStats", "(JJJJIIIII)V");
|
||||
mids.connUpdateSetInfo = jniGetMethodID(env, cls.conn_update, "setInfo", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V");
|
||||
mids.connUpdateSetPayload = jniGetMethodID(env, cls.conn_update, "setPayload", "(Ljava/util/ArrayList;Z)V");
|
||||
mids.statsInit = jniGetMethodID(env, cls.stats, "<init>", "()V");
|
||||
|
||||
@ -1071,6 +1071,8 @@ void pd_account_stats(pcapdroid_t *pd, pkt_context_t *pctx) {
|
||||
zdtun_pkt_t *pkt = pctx->pkt;
|
||||
pd_conn_t *data = pctx->data;
|
||||
|
||||
data->payload_length += pkt->l7_len;
|
||||
|
||||
if(pctx->is_tx) {
|
||||
data->sent_pkts++;
|
||||
data->sent_bytes += pkt->len;
|
||||
|
||||
@ -90,6 +90,7 @@ typedef struct {
|
||||
|
||||
jlong first_seen;
|
||||
jlong last_seen;
|
||||
jlong payload_length;
|
||||
jlong sent_bytes;
|
||||
jlong rcvd_bytes;
|
||||
jint sent_pkts;
|
||||
|
||||
@ -123,7 +123,6 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/l7proto"
|
||||
tools:tint="@color/ok"
|
||||
android:contentDescription="@string/encrypted"
|
||||
tools:src="@drawable/ic_lock_open" />
|
||||
|
||||
<TextView
|
||||
|
||||
@ -270,25 +270,61 @@
|
||||
android:textIsSelectable="true"
|
||||
tools:text="ASN_1 - Public" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:id="@+id/decryption_status_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.25"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/bytes" />
|
||||
android:text="@string/decryption" />
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.75"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/decryption_icon"
|
||||
tools:tint="@color/ok"
|
||||
tools:src="@drawable/ic_lock_open"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="-2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:paddingVertical="2dp" />
|
||||
<TextView
|
||||
android:id="@+id/detail_decryption_status"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.75"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="Decrypted" />
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginTop="16dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.25"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/network_traffic" />
|
||||
<TextView
|
||||
android:id="@+id/detail_bytes"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.75"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="1.5 MB down - 0.1 up" />
|
||||
tools:text="1.5 MB received - 0.1 sent" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
@ -328,7 +364,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.75"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="1.1 K down - 98 down" />
|
||||
tools:text="1.1 K received - 98 sent" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
@ -353,6 +389,25 @@
|
||||
tools:text="11 pkts" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.25"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/payload" />
|
||||
<TextView
|
||||
android:id="@+id/detail_payload"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.75"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="0.6 MB" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
|
||||
@ -292,7 +292,7 @@
|
||||
<string name="export_failed">Export failed</string>
|
||||
<string name="connection">Connection</string>
|
||||
<string name="encrypted">Encrypted</string>
|
||||
<string name="cleartext">Cleartext</string>
|
||||
<string name="not_encrypted">Not encrypted</string>
|
||||
<string name="request">Request</string>
|
||||
<string name="response">Response</string>
|
||||
<string name="overview">Overview</string>
|
||||
@ -301,7 +301,7 @@
|
||||
<string name="payload_truncated">Payload is truncated. Enable \"%1$s\" to show it in full</string>
|
||||
<string name="websocket">WebSocket</string>
|
||||
<string name="http">HTTP</string>
|
||||
<string name="raw_payload">Raw</string>
|
||||
<string name="payload">Payload</string>
|
||||
<string name="tx_direction">TX</string>
|
||||
<string name="rx_direction">RX</string>
|
||||
<string name="full_payload">Full payload</string>
|
||||
@ -311,4 +311,6 @@
|
||||
<string name="decryption">Decryption</string>
|
||||
<string name="decryption_filter">Decryption: %1$s</string>
|
||||
<string name="in_progress">In progress</string>
|
||||
<string name="connection_start_not_seen">PCAPdroid has not seen the start of this connection. Some information may be missing</string>
|
||||
<string name="network_traffic">Traffic</string>
|
||||
</resources>
|
||||
|
||||
@ -446,7 +446,7 @@ public class ConnectionsAdapterTest {
|
||||
ConnectionUpdate update = new ConnectionUpdate(incr_id);
|
||||
|
||||
if(tp.equals(UpdateType.UPDATE_STATS))
|
||||
update.setStats(0, 10, 10, 1, 1,
|
||||
update.setStats(0, 0, 10, 10, 1, 1,
|
||||
0, 0, ConnectionDescriptor.CONN_STATUS_CONNECTED);
|
||||
else
|
||||
update.setInfo("example.org", null, "TLS", ConnectionUpdate.UPDATE_INFO_FLAG_ENCRYPTED_L7);
|
||||
@ -525,4 +525,4 @@ public class ConnectionsAdapterTest {
|
||||
|
||||
return notified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user