Simplify messages reader/writer
In Java, control messages were parsed using manual buffering, which was convoluted and error-prone. Instead, read the socket directly through a DataInputStream and a BufferedInputStream. Symmetrically, use a DataOutputStream and a BufferedOutputStream to write messages.
This commit is contained in:
parent
3b241af3f6
commit
21b412cd98
@ -3,31 +3,22 @@ package com.genymobile.scrcpy.control;
|
|||||||
import android.net.LocalSocket;
|
import android.net.LocalSocket;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public final class ControlChannel {
|
public final class ControlChannel {
|
||||||
private final InputStream inputStream;
|
|
||||||
private final OutputStream outputStream;
|
|
||||||
|
|
||||||
private final ControlMessageReader reader = new ControlMessageReader();
|
private final ControlMessageReader reader;
|
||||||
private final DeviceMessageWriter writer = new DeviceMessageWriter();
|
private final DeviceMessageWriter writer;
|
||||||
|
|
||||||
public ControlChannel(LocalSocket controlSocket) throws IOException {
|
public ControlChannel(LocalSocket controlSocket) throws IOException {
|
||||||
this.inputStream = controlSocket.getInputStream();
|
reader = new ControlMessageReader(controlSocket.getInputStream());
|
||||||
this.outputStream = controlSocket.getOutputStream();
|
writer = new DeviceMessageWriter(controlSocket.getOutputStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControlMessage recv() throws IOException {
|
public ControlMessage recv() throws IOException {
|
||||||
ControlMessage msg = reader.next();
|
return reader.read();
|
||||||
while (msg == null) {
|
|
||||||
reader.readFrom(inputStream);
|
|
||||||
msg = reader.next();
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void send(DeviceMessage msg) throws IOException {
|
public void send(DeviceMessage msg) throws IOException {
|
||||||
writer.writeTo(msg, outputStream);
|
writer.write(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,259 +1,152 @@
|
|||||||
package com.genymobile.scrcpy.control;
|
package com.genymobile.scrcpy.control;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.util.Binary;
|
import com.genymobile.scrcpy.util.Binary;
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
|
||||||
import com.genymobile.scrcpy.device.Position;
|
import com.genymobile.scrcpy.device.Position;
|
||||||
|
|
||||||
import java.io.EOFException;
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public class ControlMessageReader {
|
public class ControlMessageReader {
|
||||||
|
|
||||||
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
|
||||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 31;
|
|
||||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
|
||||||
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
|
||||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
|
||||||
static final int GET_CLIPBOARD_LENGTH = 1;
|
|
||||||
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
|
|
||||||
static final int UHID_CREATE_FIXED_PAYLOAD_LENGTH = 4;
|
|
||||||
static final int UHID_INPUT_FIXED_PAYLOAD_LENGTH = 4;
|
|
||||||
|
|
||||||
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
||||||
|
|
||||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 14; // type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 14; // type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
|
||||||
public static final int INJECT_TEXT_MAX_LENGTH = 300;
|
public static final int INJECT_TEXT_MAX_LENGTH = 300;
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
|
private final DataInputStream dis;
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
|
||||||
|
|
||||||
public ControlMessageReader() {
|
public ControlMessageReader(InputStream rawInputStream) {
|
||||||
// invariant: the buffer is always in "get" mode
|
dis = new DataInputStream(new BufferedInputStream(rawInputStream));
|
||||||
buffer.limit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFull() {
|
public ControlMessage read() throws IOException {
|
||||||
return buffer.remaining() == rawBuffer.length;
|
int type = dis.readUnsignedByte();
|
||||||
}
|
|
||||||
|
|
||||||
public void readFrom(InputStream input) throws IOException {
|
|
||||||
if (isFull()) {
|
|
||||||
throw new IllegalStateException("Buffer full, call next() to consume");
|
|
||||||
}
|
|
||||||
buffer.compact();
|
|
||||||
int head = buffer.position();
|
|
||||||
int r = input.read(rawBuffer, head, rawBuffer.length - head);
|
|
||||||
if (r == -1) {
|
|
||||||
throw new EOFException("Controller socket closed");
|
|
||||||
}
|
|
||||||
buffer.position(head + r);
|
|
||||||
buffer.flip();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ControlMessage next() {
|
|
||||||
if (!buffer.hasRemaining()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int savedPosition = buffer.position();
|
|
||||||
|
|
||||||
int type = buffer.get();
|
|
||||||
ControlMessage msg;
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ControlMessage.TYPE_INJECT_KEYCODE:
|
case ControlMessage.TYPE_INJECT_KEYCODE:
|
||||||
msg = parseInjectKeycode();
|
return parseInjectKeycode();
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_INJECT_TEXT:
|
case ControlMessage.TYPE_INJECT_TEXT:
|
||||||
msg = parseInjectText();
|
return parseInjectText();
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
|
case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
|
||||||
msg = parseInjectTouchEvent();
|
return parseInjectTouchEvent();
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||||
msg = parseInjectScrollEvent();
|
return parseInjectScrollEvent();
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
msg = parseBackOrScreenOnEvent();
|
return parseBackOrScreenOnEvent();
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
msg = parseGetClipboard();
|
return parseGetClipboard();
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
msg = parseSetClipboard();
|
return parseSetClipboard();
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||||
msg = parseSetScreenPowerMode();
|
return parseSetScreenPowerMode();
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
||||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||||
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||||
msg = ControlMessage.createEmpty(type);
|
return ControlMessage.createEmpty(type);
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_UHID_CREATE:
|
case ControlMessage.TYPE_UHID_CREATE:
|
||||||
msg = parseUhidCreate();
|
return parseUhidCreate();
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_UHID_INPUT:
|
case ControlMessage.TYPE_UHID_INPUT:
|
||||||
msg = parseUhidInput();
|
return parseUhidInput();
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
Ln.w("Unknown event type: " + type);
|
throw new ControlProtocolException("Unknown event type: " + type);
|
||||||
msg = null;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg == null) {
|
|
||||||
// failure, reset savedPosition
|
|
||||||
buffer.position(savedPosition);
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseInjectKeycode() {
|
private ControlMessage parseInjectKeycode() throws IOException {
|
||||||
if (buffer.remaining() < INJECT_KEYCODE_PAYLOAD_LENGTH) {
|
int action = dis.readUnsignedByte();
|
||||||
return null;
|
int keycode = dis.readInt();
|
||||||
}
|
int repeat = dis.readInt();
|
||||||
int action = Binary.toUnsigned(buffer.get());
|
int metaState = dis.readInt();
|
||||||
int keycode = buffer.getInt();
|
|
||||||
int repeat = buffer.getInt();
|
|
||||||
int metaState = buffer.getInt();
|
|
||||||
return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState);
|
return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int parseBufferLength(int sizeBytes) {
|
private int parseBufferLength(int sizeBytes) throws IOException {
|
||||||
assert sizeBytes > 0 && sizeBytes <= 4;
|
assert sizeBytes > 0 && sizeBytes <= 4;
|
||||||
if (buffer.remaining() < sizeBytes) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int value = 0;
|
int value = 0;
|
||||||
for (int i = 0; i < sizeBytes; ++i) {
|
for (int i = 0; i < sizeBytes; ++i) {
|
||||||
value = (value << 8) | (buffer.get() & 0xFF);
|
value = (value << 8) | dis.readUnsignedByte();
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String parseString() {
|
private String parseString() throws IOException {
|
||||||
int len = parseBufferLength(4);
|
byte[] data = parseByteArray(4);
|
||||||
if (len == -1 || buffer.remaining() < len) {
|
return new String(data, StandardCharsets.UTF_8);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int position = buffer.position();
|
|
||||||
// Move the buffer position to consume the text
|
|
||||||
buffer.position(position + len);
|
|
||||||
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] parseByteArray(int sizeBytes) {
|
private byte[] parseByteArray(int sizeBytes) throws IOException {
|
||||||
int len = parseBufferLength(sizeBytes);
|
int len = parseBufferLength(sizeBytes);
|
||||||
if (len == -1 || buffer.remaining() < len) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
byte[] data = new byte[len];
|
byte[] data = new byte[len];
|
||||||
buffer.get(data);
|
dis.readFully(data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseInjectText() {
|
private ControlMessage parseInjectText() throws IOException {
|
||||||
String text = parseString();
|
String text = parseString();
|
||||||
if (text == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ControlMessage.createInjectText(text);
|
return ControlMessage.createInjectText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseInjectTouchEvent() {
|
private ControlMessage parseInjectTouchEvent() throws IOException {
|
||||||
if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {
|
int action = dis.readUnsignedByte();
|
||||||
return null;
|
long pointerId = dis.readLong();
|
||||||
}
|
Position position = parsePosition();
|
||||||
int action = Binary.toUnsigned(buffer.get());
|
float pressure = Binary.u16FixedPointToFloat(dis.readShort());
|
||||||
long pointerId = buffer.getLong();
|
int actionButton = dis.readInt();
|
||||||
Position position = readPosition(buffer);
|
int buttons = dis.readInt();
|
||||||
float pressure = Binary.u16FixedPointToFloat(buffer.getShort());
|
|
||||||
int actionButton = buffer.getInt();
|
|
||||||
int buttons = buffer.getInt();
|
|
||||||
return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, actionButton, buttons);
|
return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, actionButton, buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseInjectScrollEvent() {
|
private ControlMessage parseInjectScrollEvent() throws IOException {
|
||||||
if (buffer.remaining() < INJECT_SCROLL_EVENT_PAYLOAD_LENGTH) {
|
Position position = parsePosition();
|
||||||
return null;
|
float hScroll = Binary.i16FixedPointToFloat(dis.readShort());
|
||||||
}
|
float vScroll = Binary.i16FixedPointToFloat(dis.readShort());
|
||||||
Position position = readPosition(buffer);
|
int buttons = dis.readInt();
|
||||||
float hScroll = Binary.i16FixedPointToFloat(buffer.getShort());
|
|
||||||
float vScroll = Binary.i16FixedPointToFloat(buffer.getShort());
|
|
||||||
int buttons = buffer.getInt();
|
|
||||||
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
|
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseBackOrScreenOnEvent() {
|
private ControlMessage parseBackOrScreenOnEvent() throws IOException {
|
||||||
if (buffer.remaining() < BACK_OR_SCREEN_ON_LENGTH) {
|
int action = dis.readUnsignedByte();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int action = Binary.toUnsigned(buffer.get());
|
|
||||||
return ControlMessage.createBackOrScreenOn(action);
|
return ControlMessage.createBackOrScreenOn(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseGetClipboard() {
|
private ControlMessage parseGetClipboard() throws IOException {
|
||||||
if (buffer.remaining() < GET_CLIPBOARD_LENGTH) {
|
int copyKey = dis.readUnsignedByte();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int copyKey = Binary.toUnsigned(buffer.get());
|
|
||||||
return ControlMessage.createGetClipboard(copyKey);
|
return ControlMessage.createGetClipboard(copyKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseSetClipboard() {
|
private ControlMessage parseSetClipboard() throws IOException {
|
||||||
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
long sequence = dis.readLong();
|
||||||
return null;
|
boolean paste = dis.readByte() != 0;
|
||||||
}
|
|
||||||
long sequence = buffer.getLong();
|
|
||||||
boolean paste = buffer.get() != 0;
|
|
||||||
String text = parseString();
|
String text = parseString();
|
||||||
if (text == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ControlMessage.createSetClipboard(sequence, text, paste);
|
return ControlMessage.createSetClipboard(sequence, text, paste);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseSetScreenPowerMode() {
|
private ControlMessage parseSetScreenPowerMode() throws IOException {
|
||||||
if (buffer.remaining() < SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH) {
|
int mode = dis.readUnsignedByte();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int mode = buffer.get();
|
|
||||||
return ControlMessage.createSetScreenPowerMode(mode);
|
return ControlMessage.createSetScreenPowerMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseUhidCreate() {
|
private ControlMessage parseUhidCreate() throws IOException {
|
||||||
if (buffer.remaining() < UHID_CREATE_FIXED_PAYLOAD_LENGTH) {
|
int id = dis.readUnsignedShort();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int id = buffer.getShort();
|
|
||||||
byte[] data = parseByteArray(2);
|
byte[] data = parseByteArray(2);
|
||||||
if (data == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ControlMessage.createUhidCreate(id, data);
|
return ControlMessage.createUhidCreate(id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseUhidInput() {
|
private ControlMessage parseUhidInput() throws IOException {
|
||||||
if (buffer.remaining() < UHID_INPUT_FIXED_PAYLOAD_LENGTH) {
|
int id = dis.readUnsignedShort();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int id = buffer.getShort();
|
|
||||||
byte[] data = parseByteArray(2);
|
byte[] data = parseByteArray(2);
|
||||||
if (data == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ControlMessage.createUhidInput(id, data);
|
return ControlMessage.createUhidInput(id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Position readPosition(ByteBuffer buffer) {
|
private Position parsePosition() throws IOException {
|
||||||
int x = buffer.getInt();
|
int x = dis.readInt();
|
||||||
int y = buffer.getInt();
|
int y = dis.readInt();
|
||||||
int screenWidth = Binary.toUnsigned(buffer.getShort());
|
int screenWidth = dis.readUnsignedShort();
|
||||||
int screenHeight = Binary.toUnsigned(buffer.getShort());
|
int screenHeight = dis.readUnsignedShort();
|
||||||
return new Position(x, y, screenWidth, screenHeight);
|
return new Position(x, y, screenWidth, screenHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.genymobile.scrcpy.control;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class ControlProtocolException extends IOException {
|
||||||
|
public ControlProtocolException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package com.genymobile.scrcpy.control;
|
package com.genymobile.scrcpy.control;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
|
||||||
import com.genymobile.scrcpy.util.StringUtils;
|
import com.genymobile.scrcpy.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public class DeviceMessageWriter {
|
public class DeviceMessageWriter {
|
||||||
@ -13,35 +13,35 @@ public class DeviceMessageWriter {
|
|||||||
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
||||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 1 byte; length: 4 bytes
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 1 byte; length: 4 bytes
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
|
private final DataOutputStream dos;
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
|
||||||
|
|
||||||
public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
|
public DeviceMessageWriter(OutputStream rawOutputStream) {
|
||||||
buffer.clear();
|
dos = new DataOutputStream(new BufferedOutputStream(rawOutputStream));
|
||||||
buffer.put((byte) msg.getType());
|
}
|
||||||
switch (msg.getType()) {
|
|
||||||
|
public void write(DeviceMessage msg) throws IOException {
|
||||||
|
int type = msg.getType();
|
||||||
|
dos.writeByte(type);
|
||||||
|
switch (type) {
|
||||||
case DeviceMessage.TYPE_CLIPBOARD:
|
case DeviceMessage.TYPE_CLIPBOARD:
|
||||||
String text = msg.getText();
|
String text = msg.getText();
|
||||||
byte[] raw = text.getBytes(StandardCharsets.UTF_8);
|
byte[] raw = text.getBytes(StandardCharsets.UTF_8);
|
||||||
int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH);
|
int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH);
|
||||||
buffer.putInt(len);
|
dos.writeInt(len);
|
||||||
buffer.put(raw, 0, len);
|
dos.write(raw, 0, len);
|
||||||
output.write(rawBuffer, 0, buffer.position());
|
|
||||||
break;
|
break;
|
||||||
case DeviceMessage.TYPE_ACK_CLIPBOARD:
|
case DeviceMessage.TYPE_ACK_CLIPBOARD:
|
||||||
buffer.putLong(msg.getSequence());
|
dos.writeLong(msg.getSequence());
|
||||||
output.write(rawBuffer, 0, buffer.position());
|
|
||||||
break;
|
break;
|
||||||
case DeviceMessage.TYPE_UHID_OUTPUT:
|
case DeviceMessage.TYPE_UHID_OUTPUT:
|
||||||
buffer.putShort((short) msg.getId());
|
dos.writeShort(msg.getId());
|
||||||
byte[] data = msg.getData();
|
byte[] data = msg.getData();
|
||||||
buffer.putShort((short) data.length);
|
dos.writeShort(data.length);
|
||||||
buffer.put(data);
|
dos.write(data);
|
||||||
output.write(rawBuffer, 0, buffer.position());
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Ln.w("Unknown device message: " + msg.getType());
|
throw new ControlProtocolException("Unknown event type: " + type);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
dos.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import org.junit.Test;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -18,8 +19,6 @@ public class ControlMessageReaderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseKeycodeEvent() throws IOException {
|
public void testParseKeycodeEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
@ -29,23 +28,21 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
// The message type (1 byte) does not count
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
Assert.assertEquals(ControlMessageReader.INJECT_KEYCODE_PAYLOAD_LENGTH, packet.length - 1);
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
ControlMessage event = reader.next();
|
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
||||||
Assert.assertEquals(5, event.getRepeat());
|
Assert.assertEquals(5, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseTextEvent() throws IOException {
|
public void testParseTextEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
||||||
@ -54,17 +51,18 @@ public class ControlMessageReaderTest {
|
|||||||
dos.write(text);
|
dos.write(text);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_TEXT, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_TEXT, event.getType());
|
||||||
Assert.assertEquals("testé", event.getText());
|
Assert.assertEquals("testé", event.getText());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseLongTextEvent() throws IOException {
|
public void testParseLongTextEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
||||||
@ -74,17 +72,18 @@ public class ControlMessageReaderTest {
|
|||||||
dos.write(text);
|
dos.write(text);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_TEXT, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_TEXT, event.getType());
|
||||||
Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText());
|
Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseTouchEvent() throws IOException {
|
public void testParseTouchEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_TOUCH_EVENT);
|
dos.writeByte(ControlMessage.TYPE_INJECT_TOUCH_EVENT);
|
||||||
@ -100,12 +99,10 @@ public class ControlMessageReaderTest {
|
|||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
// The message type (1 byte) does not count
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
Assert.assertEquals(ControlMessageReader.INJECT_TOUCH_EVENT_PAYLOAD_LENGTH, packet.length - 1);
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
ControlMessage event = reader.next();
|
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_TOUCH_EVENT, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_TOUCH_EVENT, event.getType());
|
||||||
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
||||||
Assert.assertEquals(-42, event.getPointerId());
|
Assert.assertEquals(-42, event.getPointerId());
|
||||||
@ -116,12 +113,12 @@ public class ControlMessageReaderTest {
|
|||||||
Assert.assertEquals(1f, event.getPressure(), 0f); // must be exact
|
Assert.assertEquals(1f, event.getPressure(), 0f); // must be exact
|
||||||
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getActionButton());
|
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getActionButton());
|
||||||
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getButtons());
|
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getButtons());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseScrollEvent() throws IOException {
|
public void testParseScrollEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_SCROLL_EVENT);
|
dos.writeByte(ControlMessage.TYPE_INJECT_SCROLL_EVENT);
|
||||||
@ -132,15 +129,12 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeShort(0); // 0.0f encoded as i16
|
dos.writeShort(0); // 0.0f encoded as i16
|
||||||
dos.writeShort(0x8000); // -1.0f encoded as i16
|
dos.writeShort(0x8000); // -1.0f encoded as i16
|
||||||
dos.writeInt(1);
|
dos.writeInt(1);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
// The message type (1 byte) does not count
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
Assert.assertEquals(ControlMessageReader.INJECT_SCROLL_EVENT_PAYLOAD_LENGTH, packet.length - 1);
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
ControlMessage event = reader.next();
|
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_SCROLL_EVENT, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_SCROLL_EVENT, event.getType());
|
||||||
Assert.assertEquals(260, event.getPosition().getPoint().getX());
|
Assert.assertEquals(260, event.getPosition().getPoint().getX());
|
||||||
Assert.assertEquals(1026, event.getPosition().getPoint().getY());
|
Assert.assertEquals(1026, event.getPosition().getPoint().getY());
|
||||||
@ -149,96 +143,96 @@ public class ControlMessageReaderTest {
|
|||||||
Assert.assertEquals(0f, event.getHScroll(), 0f);
|
Assert.assertEquals(0f, event.getHScroll(), 0f);
|
||||||
Assert.assertEquals(-1f, event.getVScroll(), 0f);
|
Assert.assertEquals(-1f, event.getVScroll(), 0f);
|
||||||
Assert.assertEquals(1, event.getButtons());
|
Assert.assertEquals(1, event.getButtons());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseBackOrScreenOnEvent() throws IOException {
|
public void testParseBackOrScreenOnEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_BACK_OR_SCREEN_ON);
|
dos.writeByte(ControlMessage.TYPE_BACK_OR_SCREEN_ON);
|
||||||
dos.writeByte(KeyEvent.ACTION_UP);
|
dos.writeByte(KeyEvent.ACTION_UP);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType());
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseExpandNotificationPanelEvent() throws IOException {
|
public void testParseExpandNotificationPanelEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL);
|
dos.writeByte(ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL, event.getType());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseExpandSettingsPanelEvent() throws IOException {
|
public void testParseExpandSettingsPanelEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_EXPAND_SETTINGS_PANEL);
|
dos.writeByte(ControlMessage.TYPE_EXPAND_SETTINGS_PANEL);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_EXPAND_SETTINGS_PANEL, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_EXPAND_SETTINGS_PANEL, event.getType());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseCollapsePanelsEvent() throws IOException {
|
public void testParseCollapsePanelsEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_COLLAPSE_PANELS);
|
dos.writeByte(ControlMessage.TYPE_COLLAPSE_PANELS);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_COLLAPSE_PANELS, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_COLLAPSE_PANELS, event.getType());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseGetClipboardEvent() throws IOException {
|
public void testParseGetClipboardEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
|
dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
|
||||||
dos.writeByte(ControlMessage.COPY_KEY_COPY);
|
dos.writeByte(ControlMessage.COPY_KEY_COPY);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
|
||||||
Assert.assertEquals(ControlMessage.COPY_KEY_COPY, event.getCopyKey());
|
Assert.assertEquals(ControlMessage.COPY_KEY_COPY, event.getCopyKey());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseSetClipboardEvent() throws IOException {
|
public void testParseSetClipboardEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||||
@ -247,22 +241,22 @@ public class ControlMessageReaderTest {
|
|||||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||||
dos.writeInt(text.length);
|
dos.writeInt(text.length);
|
||||||
dos.write(text);
|
dos.write(text);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||||
Assert.assertEquals(0x0102030405060708L, event.getSequence());
|
Assert.assertEquals(0x0102030405060708L, event.getSequence());
|
||||||
Assert.assertEquals("testé", event.getText());
|
Assert.assertEquals("testé", event.getText());
|
||||||
Assert.assertTrue(event.getPaste());
|
Assert.assertTrue(event.getPaste());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseBigSetClipboardEvent() throws IOException {
|
public void testParseBigSetClipboardEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||||
@ -278,56 +272,54 @@ public class ControlMessageReaderTest {
|
|||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||||
Assert.assertEquals(0x0807060504030201L, event.getSequence());
|
Assert.assertEquals(0x0807060504030201L, event.getSequence());
|
||||||
Assert.assertEquals(text, event.getText());
|
Assert.assertEquals(text, event.getText());
|
||||||
Assert.assertTrue(event.getPaste());
|
Assert.assertTrue(event.getPaste());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseSetScreenPowerMode() throws IOException {
|
public void testParseSetScreenPowerMode() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_SET_SCREEN_POWER_MODE);
|
dos.writeByte(ControlMessage.TYPE_SET_SCREEN_POWER_MODE);
|
||||||
dos.writeByte(Device.POWER_MODE_NORMAL);
|
dos.writeByte(Device.POWER_MODE_NORMAL);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
// The message type (1 byte) does not count
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
Assert.assertEquals(ControlMessageReader.SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH, packet.length - 1);
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
ControlMessage event = reader.next();
|
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_SET_SCREEN_POWER_MODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_SET_SCREEN_POWER_MODE, event.getType());
|
||||||
Assert.assertEquals(Device.POWER_MODE_NORMAL, event.getAction());
|
Assert.assertEquals(Device.POWER_MODE_NORMAL, event.getAction());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseRotateDevice() throws IOException {
|
public void testParseRotateDevice() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_ROTATE_DEVICE);
|
dos.writeByte(ControlMessage.TYPE_ROTATE_DEVICE);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_ROTATE_DEVICE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_ROTATE_DEVICE, event.getType());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseUhidCreate() throws IOException {
|
public void testParseUhidCreate() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_UHID_CREATE);
|
dos.writeByte(ControlMessage.TYPE_UHID_CREATE);
|
||||||
@ -335,21 +327,21 @@ public class ControlMessageReaderTest {
|
|||||||
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
||||||
dos.writeShort(data.length); // size
|
dos.writeShort(data.length); // size
|
||||||
dos.write(data);
|
dos.write(data);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType());
|
||||||
Assert.assertEquals(42, event.getId());
|
Assert.assertEquals(42, event.getId());
|
||||||
Assert.assertArrayEquals(data, event.getData());
|
Assert.assertArrayEquals(data, event.getData());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseUhidInput() throws IOException {
|
public void testParseUhidInput() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_UHID_INPUT);
|
dos.writeByte(ControlMessage.TYPE_UHID_INPUT);
|
||||||
@ -357,37 +349,37 @@ public class ControlMessageReaderTest {
|
|||||||
byte[] data = {1, 2, 3, 4, 5};
|
byte[] data = {1, 2, 3, 4, 5};
|
||||||
dos.writeShort(data.length); // size
|
dos.writeShort(data.length); // size
|
||||||
dos.write(data);
|
dos.write(data);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_UHID_INPUT, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_UHID_INPUT, event.getType());
|
||||||
Assert.assertEquals(42, event.getId());
|
Assert.assertEquals(42, event.getId());
|
||||||
Assert.assertArrayEquals(data, event.getData());
|
Assert.assertArrayEquals(data, event.getData());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseOpenHardKeyboardSettings() throws IOException {
|
public void testParseOpenHardKeyboardSettings() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS);
|
dos.writeByte(ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
ControlMessage event = reader.next();
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS, event.getType());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMultiEvents() throws IOException {
|
public void testMultiEvents() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
|
||||||
@ -404,27 +396,29 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
|
|
||||||
ControlMessage event = reader.next();
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
||||||
Assert.assertEquals(0, event.getRepeat());
|
Assert.assertEquals(0, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
|
||||||
event = reader.next();
|
event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
||||||
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
||||||
Assert.assertEquals(1, event.getRepeat());
|
Assert.assertEquals(1, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
|
||||||
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPartialEvents() throws IOException {
|
public void testPartialEvents() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
|
||||||
@ -438,31 +432,21 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeByte(MotionEvent.ACTION_DOWN);
|
dos.writeByte(MotionEvent.ACTION_DOWN);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||||
|
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||||
|
|
||||||
ControlMessage event = reader.next();
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
||||||
Assert.assertEquals(4, event.getRepeat());
|
Assert.assertEquals(4, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
|
||||||
event = reader.next();
|
try {
|
||||||
Assert.assertNull(event); // the event is not complete
|
event = reader.read();
|
||||||
|
Assert.fail("Reader did not reach EOF");
|
||||||
bos.reset();
|
} catch (EOFException e) {
|
||||||
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
// expected
|
||||||
dos.writeInt(5); // repeat
|
}
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
|
||||||
packet = bos.toByteArray();
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
|
|
||||||
// the event is now complete
|
|
||||||
event = reader.next();
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
|
||||||
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
|
||||||
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
|
||||||
Assert.assertEquals(5, event.getRepeat());
|
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,6 @@ public class DeviceMessageWriterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSerializeClipboard() throws IOException {
|
public void testSerializeClipboard() throws IOException {
|
||||||
DeviceMessageWriter writer = new DeviceMessageWriter();
|
|
||||||
|
|
||||||
String text = "aéûoç";
|
String text = "aéûoç";
|
||||||
byte[] data = text.getBytes(StandardCharsets.UTF_8);
|
byte[] data = text.getBytes(StandardCharsets.UTF_8);
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
@ -21,12 +19,13 @@ public class DeviceMessageWriterTest {
|
|||||||
dos.writeByte(DeviceMessage.TYPE_CLIPBOARD);
|
dos.writeByte(DeviceMessage.TYPE_CLIPBOARD);
|
||||||
dos.writeInt(data.length);
|
dos.writeInt(data.length);
|
||||||
dos.write(data);
|
dos.write(data);
|
||||||
|
|
||||||
byte[] expected = bos.toByteArray();
|
byte[] expected = bos.toByteArray();
|
||||||
|
|
||||||
DeviceMessage msg = DeviceMessage.createClipboard(text);
|
|
||||||
bos = new ByteArrayOutputStream();
|
bos = new ByteArrayOutputStream();
|
||||||
writer.writeTo(msg, bos);
|
DeviceMessageWriter writer = new DeviceMessageWriter(bos);
|
||||||
|
|
||||||
|
DeviceMessage msg = DeviceMessage.createClipboard(text);
|
||||||
|
writer.write(msg);
|
||||||
|
|
||||||
byte[] actual = bos.toByteArray();
|
byte[] actual = bos.toByteArray();
|
||||||
|
|
||||||
@ -35,18 +34,17 @@ public class DeviceMessageWriterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSerializeAckSetClipboard() throws IOException {
|
public void testSerializeAckSetClipboard() throws IOException {
|
||||||
DeviceMessageWriter writer = new DeviceMessageWriter();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(DeviceMessage.TYPE_ACK_CLIPBOARD);
|
dos.writeByte(DeviceMessage.TYPE_ACK_CLIPBOARD);
|
||||||
dos.writeLong(0x0102030405060708L);
|
dos.writeLong(0x0102030405060708L);
|
||||||
|
|
||||||
byte[] expected = bos.toByteArray();
|
byte[] expected = bos.toByteArray();
|
||||||
|
|
||||||
DeviceMessage msg = DeviceMessage.createAckClipboard(0x0102030405060708L);
|
|
||||||
bos = new ByteArrayOutputStream();
|
bos = new ByteArrayOutputStream();
|
||||||
writer.writeTo(msg, bos);
|
DeviceMessageWriter writer = new DeviceMessageWriter(bos);
|
||||||
|
|
||||||
|
DeviceMessage msg = DeviceMessage.createAckClipboard(0x0102030405060708L);
|
||||||
|
writer.write(msg);
|
||||||
|
|
||||||
byte[] actual = bos.toByteArray();
|
byte[] actual = bos.toByteArray();
|
||||||
|
|
||||||
@ -55,8 +53,6 @@ public class DeviceMessageWriterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSerializeUhidOutput() throws IOException {
|
public void testSerializeUhidOutput() throws IOException {
|
||||||
DeviceMessageWriter writer = new DeviceMessageWriter();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(DeviceMessage.TYPE_UHID_OUTPUT);
|
dos.writeByte(DeviceMessage.TYPE_UHID_OUTPUT);
|
||||||
@ -64,12 +60,13 @@ public class DeviceMessageWriterTest {
|
|||||||
byte[] data = {1, 2, 3, 4, 5};
|
byte[] data = {1, 2, 3, 4, 5};
|
||||||
dos.writeShort(data.length);
|
dos.writeShort(data.length);
|
||||||
dos.write(data);
|
dos.write(data);
|
||||||
|
|
||||||
byte[] expected = bos.toByteArray();
|
byte[] expected = bos.toByteArray();
|
||||||
|
|
||||||
DeviceMessage msg = DeviceMessage.createUhidOutput(42, data);
|
|
||||||
bos = new ByteArrayOutputStream();
|
bos = new ByteArrayOutputStream();
|
||||||
writer.writeTo(msg, bos);
|
DeviceMessageWriter writer = new DeviceMessageWriter(bos);
|
||||||
|
|
||||||
|
DeviceMessage msg = DeviceMessage.createUhidOutput(42, data);
|
||||||
|
writer.write(msg);
|
||||||
|
|
||||||
byte[] actual = bos.toByteArray();
|
byte[] actual = bos.toByteArray();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user