Adding ability to control how the USB data is dumped.
Also added tracing to android.UsbDevice creation. Test: Try each dump command with CALYX Pat USB DAC & Dragonfly Red USB DAC Change-Id: I2ec05dfa9c544d0c50ee59e3b990e9010cbe0aa0
This commit is contained in:
@@ -28,6 +28,7 @@ import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.usb.descriptors.UsbDescriptor;
|
||||
import com.android.server.usb.descriptors.UsbDescriptorParser;
|
||||
import com.android.server.usb.descriptors.UsbDeviceDescriptor;
|
||||
import com.android.server.usb.descriptors.report.TextReportCanvas;
|
||||
@@ -84,7 +85,10 @@ public class UsbHostManager {
|
||||
String mDeviceAddress;
|
||||
|
||||
static final int CONNECT = 0;
|
||||
static final int DISCONNECT = 1;
|
||||
static final int CONNECT_BADPARSE = 1;
|
||||
static final int CONNECT_BADDEVICE = 2;
|
||||
static final int DISCONNECT = -1;
|
||||
|
||||
final int mMode;
|
||||
final byte[] mDescriptors;
|
||||
|
||||
@@ -100,8 +104,8 @@ public class UsbHostManager {
|
||||
}
|
||||
|
||||
void dumpShort(IndentingPrintWriter pw) {
|
||||
if (mMode == CONNECT) {
|
||||
pw.println(formatTime() + " Connect " + mDeviceAddress);
|
||||
if (mMode != DISCONNECT) {
|
||||
pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode);
|
||||
UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors);
|
||||
|
||||
UsbDeviceDescriptor deviceDescriptor = parser.getDeviceDescriptor();
|
||||
@@ -115,9 +119,9 @@ public class UsbHostManager {
|
||||
}
|
||||
}
|
||||
|
||||
void dumpLong(IndentingPrintWriter pw) {
|
||||
if (mMode == CONNECT) {
|
||||
pw.println(formatTime() + " Connect " + mDeviceAddress);
|
||||
void dumpTree(IndentingPrintWriter pw) {
|
||||
if (mMode != DISCONNECT) {
|
||||
pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode);
|
||||
UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree();
|
||||
@@ -131,6 +135,55 @@ public class UsbHostManager {
|
||||
pw.println(formatTime() + " Disconnect " + mDeviceAddress);
|
||||
}
|
||||
}
|
||||
|
||||
void dumpList(IndentingPrintWriter pw) {
|
||||
if (mMode != DISCONNECT) {
|
||||
pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode);
|
||||
UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
TextReportCanvas canvas = new TextReportCanvas(parser, stringBuilder);
|
||||
for (UsbDescriptor descriptor : parser.getDescriptors()) {
|
||||
descriptor.report(canvas);
|
||||
}
|
||||
pw.println(stringBuilder.toString());
|
||||
|
||||
pw.println("isHeadset[in: " + parser.isInputHeadset()
|
||||
+ " , out: " + parser.isOutputHeadset() + "]");
|
||||
} else {
|
||||
pw.println(formatTime() + " Disconnect " + mDeviceAddress);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int kDumpBytesPerLine = 16;
|
||||
|
||||
void dumpRaw(IndentingPrintWriter pw) {
|
||||
if (mMode != DISCONNECT) {
|
||||
pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode);
|
||||
int length = mDescriptors.length;
|
||||
pw.println("Raw Descriptors " + length + " bytes");
|
||||
int dataOffset = 0;
|
||||
for (int line = 0; line < length / kDumpBytesPerLine; line++) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int offset = 0; offset < kDumpBytesPerLine; offset++) {
|
||||
sb.append("0x")
|
||||
.append(String.format("0x%02X", mDescriptors[dataOffset++]))
|
||||
.append(" ");
|
||||
}
|
||||
pw.println(sb.toString());
|
||||
}
|
||||
|
||||
// remainder
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (dataOffset < length) {
|
||||
sb.append("0x")
|
||||
.append(String.format("0x%02X", mDescriptors[dataOffset++]))
|
||||
.append(" ");
|
||||
}
|
||||
pw.println(sb.toString());
|
||||
} else {
|
||||
pw.println(formatTime() + " Disconnect " + mDeviceAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -205,7 +258,7 @@ public class UsbHostManager {
|
||||
ConnectionRecord rec =
|
||||
new ConnectionRecord(deviceAddress, mode, rawDescriptors);
|
||||
mConnections.add(rec);
|
||||
if (mode == ConnectionRecord.CONNECT) {
|
||||
if (mode != ConnectionRecord.DISCONNECT) {
|
||||
mLastConnect = rec;
|
||||
}
|
||||
}
|
||||
@@ -241,31 +294,41 @@ public class UsbHostManager {
|
||||
if (parser.parseDescriptors(descriptors)) {
|
||||
|
||||
UsbDevice newDevice = parser.toAndroidUsbDevice();
|
||||
mDevices.put(deviceAddress, newDevice);
|
||||
|
||||
// It is fine to call this only for the current user as all broadcasts are sent to
|
||||
// all profiles of the user and the dialogs should only show once.
|
||||
ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
|
||||
if (usbDeviceConnectionHandler == null) {
|
||||
getCurrentUserSettings().deviceAttached(newDevice);
|
||||
if (newDevice == null) {
|
||||
Slog.e(TAG, "Couldn't create UsbDevice object.");
|
||||
// Tracking
|
||||
addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT_BADDEVICE,
|
||||
parser.getRawDescriptors());
|
||||
} else {
|
||||
getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
|
||||
usbDeviceConnectionHandler);
|
||||
mDevices.put(deviceAddress, newDevice);
|
||||
|
||||
// It is fine to call this only for the current user as all broadcasts are
|
||||
// sent to all profiles of the user and the dialogs should only show once.
|
||||
ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
|
||||
if (usbDeviceConnectionHandler == null) {
|
||||
getCurrentUserSettings().deviceAttached(newDevice);
|
||||
} else {
|
||||
getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
|
||||
usbDeviceConnectionHandler);
|
||||
}
|
||||
|
||||
// Headset?
|
||||
boolean isInputHeadset = parser.isInputHeadset();
|
||||
boolean isOutputHeadset = parser.isOutputHeadset();
|
||||
Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset
|
||||
+ " , out: " + isOutputHeadset + "]");
|
||||
|
||||
mUsbAlsaManager.usbDeviceAdded(newDevice, isInputHeadset, isOutputHeadset);
|
||||
|
||||
// Tracking
|
||||
addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
|
||||
parser.getRawDescriptors());
|
||||
}
|
||||
|
||||
// Headset?
|
||||
boolean isInputHeadset = parser.isInputHeadset();
|
||||
boolean isOutputHeadset = parser.isOutputHeadset();
|
||||
Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset
|
||||
+ " , out: " + isOutputHeadset + "]");
|
||||
|
||||
mUsbAlsaManager.usbDeviceAdded(newDevice, isInputHeadset, isOutputHeadset);
|
||||
|
||||
// Tracking
|
||||
addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
|
||||
parser.getRawDescriptors());
|
||||
} else {
|
||||
Slog.e(TAG, "Error parsing USB device descriptors for " + deviceAddress);
|
||||
// Tracking
|
||||
addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT_BADPARSE,
|
||||
parser.getRawDescriptors());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -330,7 +393,11 @@ public class UsbHostManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void dump(IndentingPrintWriter pw) {
|
||||
/**
|
||||
* Dump out various information about the state of USB device connections.
|
||||
*
|
||||
*/
|
||||
public void dump(IndentingPrintWriter pw, String[] args) {
|
||||
pw.println("USB Host State:");
|
||||
synchronized (mHandlerLock) {
|
||||
if (mUsbDeviceConnectionHandler != null) {
|
||||
@@ -342,21 +409,38 @@ public class UsbHostManager {
|
||||
pw.println(" " + name + ": " + mDevices.get(name));
|
||||
}
|
||||
|
||||
// Connections
|
||||
pw.println("" + mNumConnects + " total connects/disconnects");
|
||||
pw.println("Last " + mConnections.size() + " connections/disconnections");
|
||||
for (ConnectionRecord rec : mConnections) {
|
||||
rec.dumpShort(pw);
|
||||
}
|
||||
|
||||
if (mLastConnect != null) {
|
||||
pw.println("Last Connected USB Device:");
|
||||
mLastConnect.dumpLong(pw);
|
||||
}
|
||||
}
|
||||
|
||||
mUsbAlsaManager.dump(pw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump various descriptor data.
|
||||
*/
|
||||
public void dumpDescriptors(IndentingPrintWriter pw, String[] args) {
|
||||
if (mLastConnect != null) {
|
||||
pw.println("Last Connected USB Device:");
|
||||
if (args.length <= 1 || args[1].equals("-dump-short")) {
|
||||
mLastConnect.dumpShort(pw);
|
||||
} else if (args[1].equals("-dump-tree")) {
|
||||
mLastConnect.dumpTree(pw);
|
||||
} else if (args[1].equals("-dump-list")) {
|
||||
mLastConnect.dumpList(pw);
|
||||
} else if (args[1].equals("-dump-raw")) {
|
||||
mLastConnect.dumpRaw(pw);
|
||||
}
|
||||
} else {
|
||||
pw.println("No USB Devices have been connected.");
|
||||
}
|
||||
}
|
||||
|
||||
private native void monitorUsbHostBus();
|
||||
private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
|
||||
}
|
||||
|
||||
@@ -496,7 +496,7 @@ public class UsbService extends IUsbManager.Stub {
|
||||
mDeviceManager.dump(pw);
|
||||
}
|
||||
if (mHostManager != null) {
|
||||
mHostManager.dump(pw);
|
||||
mHostManager.dump(pw, args);
|
||||
}
|
||||
if (mPortManager != null) {
|
||||
mPortManager.dump(pw);
|
||||
@@ -504,7 +504,7 @@ public class UsbService extends IUsbManager.Stub {
|
||||
mAlsaManager.dump(pw);
|
||||
|
||||
mSettingsManager.dump(pw);
|
||||
} else if (args.length == 4 && "set-port-roles".equals(args[0])) {
|
||||
} else if ("set-port-roles".equals(args[0]) && args.length == 4) {
|
||||
final String portId = args[1];
|
||||
final int powerRole;
|
||||
switch (args[2]) {
|
||||
@@ -546,7 +546,7 @@ public class UsbService extends IUsbManager.Stub {
|
||||
pw.println();
|
||||
mPortManager.dump(pw);
|
||||
}
|
||||
} else if (args.length == 3 && "add-port".equals(args[0])) {
|
||||
} else if ("add-port".equals(args[0]) && args.length == 3) {
|
||||
final String portId = args[1];
|
||||
final int supportedModes;
|
||||
switch (args[2]) {
|
||||
@@ -571,7 +571,7 @@ public class UsbService extends IUsbManager.Stub {
|
||||
pw.println();
|
||||
mPortManager.dump(pw);
|
||||
}
|
||||
} else if (args.length == 5 && "connect-port".equals(args[0])) {
|
||||
} else if ("connect-port".equals(args[0]) && args.length == 5) {
|
||||
final String portId = args[1];
|
||||
final int mode;
|
||||
final boolean canChangeMode = args[2].endsWith("?");
|
||||
@@ -618,30 +618,32 @@ public class UsbService extends IUsbManager.Stub {
|
||||
pw.println();
|
||||
mPortManager.dump(pw);
|
||||
}
|
||||
} else if (args.length == 2 && "disconnect-port".equals(args[0])) {
|
||||
} else if ("disconnect-port".equals(args[0]) && args.length == 2) {
|
||||
final String portId = args[1];
|
||||
if (mPortManager != null) {
|
||||
mPortManager.disconnectSimulatedPort(portId, pw);
|
||||
pw.println();
|
||||
mPortManager.dump(pw);
|
||||
}
|
||||
} else if (args.length == 2 && "remove-port".equals(args[0])) {
|
||||
} else if ("remove-port".equals(args[0]) && args.length == 2) {
|
||||
final String portId = args[1];
|
||||
if (mPortManager != null) {
|
||||
mPortManager.removeSimulatedPort(portId, pw);
|
||||
pw.println();
|
||||
mPortManager.dump(pw);
|
||||
}
|
||||
} else if (args.length == 1 && "reset".equals(args[0])) {
|
||||
} else if ("reset".equals(args[0]) && args.length == 1) {
|
||||
if (mPortManager != null) {
|
||||
mPortManager.resetSimulation(pw);
|
||||
pw.println();
|
||||
mPortManager.dump(pw);
|
||||
}
|
||||
} else if (args.length == 1 && "ports".equals(args[0])) {
|
||||
} else if ("ports".equals(args[0]) && args.length == 1) {
|
||||
if (mPortManager != null) {
|
||||
mPortManager.dump(pw);
|
||||
}
|
||||
} else if ("dump-descriptors".equals(args[0])) {
|
||||
mHostManager.dumpDescriptors(pw, args);
|
||||
} else {
|
||||
pw.println("Dump current USB state or issue command:");
|
||||
pw.println(" ports");
|
||||
@@ -678,6 +680,12 @@ public class UsbService extends IUsbManager.Stub {
|
||||
pw.println(" dumpsys usb add-port \"matrix\" ufp");
|
||||
pw.println(" dumpsys usb connect-port \"matrix\" ufp sink device");
|
||||
pw.println(" dumpsys usb reset");
|
||||
pw.println();
|
||||
pw.println("Example USB device descriptors:");
|
||||
pw.println(" dumpsys usb dump-descriptors -dump-short");
|
||||
pw.println(" dumpsys usb dump-descriptors -dump-tree");
|
||||
pw.println(" dumpsys usb dump-descriptors -dump-list");
|
||||
pw.println(" dumpsys usb dump-descriptors -dump-raw");
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
|
||||
@@ -17,6 +17,7 @@ package com.android.server.usb.descriptors;
|
||||
|
||||
import android.hardware.usb.UsbConfiguration;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.server.usb.descriptors.report.ReportCanvas;
|
||||
|
||||
@@ -29,6 +30,7 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public final class UsbConfigDescriptor extends UsbDescriptor {
|
||||
private static final String TAG = "UsbConfigDescriptor";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private int mTotalLength; // 2:2 Total length in bytes of data returned
|
||||
private byte mNumInterfaces; // 4:1 Number of Interfaces
|
||||
@@ -77,10 +79,16 @@ public final class UsbConfigDescriptor extends UsbDescriptor {
|
||||
}
|
||||
|
||||
UsbConfiguration toAndroid(UsbDescriptorParser parser) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, " toAndroid()");
|
||||
}
|
||||
String name = parser.getDescriptorString(mConfigIndex);
|
||||
UsbConfiguration config = new
|
||||
UsbConfiguration(mConfigValue, name, mAttribs, mMaxPower);
|
||||
UsbInterface[] interfaces = new UsbInterface[mInterfaceDescriptors.size()];
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, " " + mInterfaceDescriptors.size() + " interfaces.");
|
||||
}
|
||||
for (int index = 0; index < mInterfaceDescriptors.size(); index++) {
|
||||
interfaces[index] = mInterfaceDescriptors.get(index).toAndroid(parser);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public final class UsbDescriptorParser {
|
||||
private static final String TAG = "UsbDescriptorParser";
|
||||
private static final boolean DEBUG = false;
|
||||
private static final boolean DEBUG = true;
|
||||
|
||||
private final String mDeviceAddr;
|
||||
|
||||
@@ -289,10 +289,15 @@ public final class UsbDescriptorParser {
|
||||
*/
|
||||
public UsbDevice toAndroidUsbDevice() {
|
||||
if (mDeviceDescriptor == null) {
|
||||
Log.e(TAG, "toAndroidUsbDevice() ERROR - No Device Descriptor");
|
||||
return null;
|
||||
}
|
||||
|
||||
return mDeviceDescriptor.toAndroid(this);
|
||||
UsbDevice device = mDeviceDescriptor.toAndroid(this);
|
||||
if (device == null) {
|
||||
Log.e(TAG, "toAndroidUsbDevice() ERROR Creating Device");
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@ package com.android.server.usb.descriptors;
|
||||
|
||||
import android.hardware.usb.UsbConfiguration;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.server.usb.descriptors.report.ReportCanvas;
|
||||
import com.android.server.usb.descriptors.report.UsbStrings;
|
||||
@@ -30,6 +31,7 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public final class UsbDeviceDescriptor extends UsbDescriptor {
|
||||
private static final String TAG = "UsbDeviceDescriptor";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
public static final int USBSPEC_1_0 = 0x0100;
|
||||
public static final int USBSPEC_1_1 = 0x0110;
|
||||
@@ -113,19 +115,30 @@ public final class UsbDeviceDescriptor extends UsbDescriptor {
|
||||
* @hide
|
||||
*/
|
||||
public UsbDevice toAndroid(UsbDescriptorParser parser) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "toAndroid()");
|
||||
}
|
||||
|
||||
String mfgName = parser.getDescriptorString(mMfgIndex);
|
||||
String prodName = parser.getDescriptorString(mProductIndex);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, " mfgName:" + mfgName + " prodName:" + prodName);
|
||||
}
|
||||
|
||||
// Create version string in "%.%" format
|
||||
String versionString =
|
||||
Integer.toString(mDeviceRelease >> 8) + "." + (mDeviceRelease & 0xFF);
|
||||
String serialStr = parser.getDescriptorString(mSerialNum);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, " versionString:" + versionString + " serialStr:" + serialStr);
|
||||
}
|
||||
|
||||
UsbDevice device = new UsbDevice(parser.getDeviceAddr(), mVendorID, mProductID,
|
||||
mDevClass, mDevSubClass,
|
||||
mProtocol, mfgName, prodName,
|
||||
versionString, serialStr);
|
||||
UsbConfiguration[] configs = new UsbConfiguration[mConfigDescriptors.size()];
|
||||
Log.d(TAG, " " + configs.length + " configs");
|
||||
for (int index = 0; index < mConfigDescriptors.size(); index++) {
|
||||
configs[index] = mConfigDescriptors.get(index).toAndroid(parser);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.android.server.usb.descriptors;
|
||||
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.server.usb.descriptors.report.ReportCanvas;
|
||||
|
||||
@@ -26,6 +27,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas;
|
||||
*/
|
||||
public class UsbEndpointDescriptor extends UsbDescriptor {
|
||||
private static final String TAG = "UsbEndpointDescriptor";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
public static final int MASK_ENDPOINT_ADDRESS = 0b000000000001111;
|
||||
public static final int MASK_ENDPOINT_DIRECTION = (byte) 0b0000000010000000;
|
||||
@@ -108,6 +110,12 @@ public class UsbEndpointDescriptor extends UsbDescriptor {
|
||||
}
|
||||
|
||||
/* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "toAndroid() type:"
|
||||
+ Integer.toHexString(mAttributes & MASK_ATTRIBS_TRANSTYPE)
|
||||
+ " sync:" + Integer.toHexString(mAttributes & MASK_ATTRIBS_SYNCTYPE)
|
||||
+ " usage:" + Integer.toHexString(mAttributes & MASK_ATTRIBS_USEAGE));
|
||||
}
|
||||
return new UsbEndpoint(mEndpointAddress, mAttributes, mPacketSize, mInterval);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package com.android.server.usb.descriptors;
|
||||
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.server.usb.descriptors.report.ReportCanvas;
|
||||
import com.android.server.usb.descriptors.report.UsbStrings;
|
||||
@@ -30,6 +31,7 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public class UsbInterfaceDescriptor extends UsbDescriptor {
|
||||
private static final String TAG = "UsbInterfaceDescriptor";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
protected int mInterfaceNumber; // 2:1 Number of Interface
|
||||
protected byte mAlternateSetting; // 3:1 Value used to select alternative setting
|
||||
@@ -93,6 +95,11 @@ public class UsbInterfaceDescriptor extends UsbDescriptor {
|
||||
}
|
||||
|
||||
UsbInterface toAndroid(UsbDescriptorParser parser) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "toAndroid() class:" + Integer.toHexString(mUsbClass)
|
||||
+ " subclass:" + Integer.toHexString(mUsbSubclass)
|
||||
+ " " + mEndpointDescriptors.size() + " endpoints.");
|
||||
}
|
||||
String name = parser.getDescriptorString(mDescrIndex);
|
||||
UsbInterface ntrface = new UsbInterface(
|
||||
mInterfaceNumber, mAlternateSetting, name, mUsbClass, mUsbSubclass, mProtocol);
|
||||
|
||||
Reference in New Issue
Block a user