Merge "Add log for anomaly." into oc-dr1-dev
am: 6666bf5605
Change-Id: I4903e3617b2fab64831b6e0ad8b5214ea5dd5317
This commit is contained in:
@@ -68,6 +68,8 @@ public class Anomaly implements Parcelable {
|
|||||||
public final int uid;
|
public final int uid;
|
||||||
public final int targetSdkVersion;
|
public final int targetSdkVersion;
|
||||||
public final long wakelockTimeMs;
|
public final long wakelockTimeMs;
|
||||||
|
public final long bluetoothScanningTimeMs;
|
||||||
|
public final int wakeupAlarmCount;
|
||||||
/**
|
/**
|
||||||
* {@code true} if background restriction is enabled
|
* {@code true} if background restriction is enabled
|
||||||
*
|
*
|
||||||
@@ -88,6 +90,8 @@ public class Anomaly implements Parcelable {
|
|||||||
wakelockTimeMs = builder.mWakeLockTimeMs;
|
wakelockTimeMs = builder.mWakeLockTimeMs;
|
||||||
targetSdkVersion = builder.mTargetSdkVersion;
|
targetSdkVersion = builder.mTargetSdkVersion;
|
||||||
backgroundRestrictionEnabled = builder.mBgRestrictionEnabled;
|
backgroundRestrictionEnabled = builder.mBgRestrictionEnabled;
|
||||||
|
bluetoothScanningTimeMs = builder.mBluetoothScanningTimeMs;
|
||||||
|
wakeupAlarmCount = builder.mWakeupAlarmCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Anomaly(Parcel in) {
|
private Anomaly(Parcel in) {
|
||||||
@@ -98,6 +102,8 @@ public class Anomaly implements Parcelable {
|
|||||||
wakelockTimeMs = in.readLong();
|
wakelockTimeMs = in.readLong();
|
||||||
targetSdkVersion = in.readInt();
|
targetSdkVersion = in.readInt();
|
||||||
backgroundRestrictionEnabled = in.readBoolean();
|
backgroundRestrictionEnabled = in.readBoolean();
|
||||||
|
wakeupAlarmCount = in.readInt();
|
||||||
|
bluetoothScanningTimeMs = in.readLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -114,6 +120,8 @@ public class Anomaly implements Parcelable {
|
|||||||
dest.writeLong(wakelockTimeMs);
|
dest.writeLong(wakelockTimeMs);
|
||||||
dest.writeInt(targetSdkVersion);
|
dest.writeInt(targetSdkVersion);
|
||||||
dest.writeBoolean(backgroundRestrictionEnabled);
|
dest.writeBoolean(backgroundRestrictionEnabled);
|
||||||
|
dest.writeInt(wakeupAlarmCount);
|
||||||
|
dest.writeLong(bluetoothScanningTimeMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -132,13 +140,36 @@ public class Anomaly implements Parcelable {
|
|||||||
&& TextUtils.equals(displayName, other.displayName)
|
&& TextUtils.equals(displayName, other.displayName)
|
||||||
&& TextUtils.equals(packageName, other.packageName)
|
&& TextUtils.equals(packageName, other.packageName)
|
||||||
&& targetSdkVersion == other.targetSdkVersion
|
&& targetSdkVersion == other.targetSdkVersion
|
||||||
&& backgroundRestrictionEnabled == other.backgroundRestrictionEnabled;
|
&& backgroundRestrictionEnabled == other.backgroundRestrictionEnabled
|
||||||
|
&& wakeupAlarmCount == other.wakeupAlarmCount
|
||||||
|
&& bluetoothScanningTimeMs == other.bluetoothScanningTimeMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(type, uid, displayName, packageName, wakelockTimeMs, targetSdkVersion,
|
return Objects.hash(type, uid, displayName, packageName, wakelockTimeMs, targetSdkVersion,
|
||||||
backgroundRestrictionEnabled);
|
backgroundRestrictionEnabled, wakeupAlarmCount, bluetoothScanningTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "type=" + toAnomalyTypeText(type) + " uid=" + uid + " package=" + packageName +
|
||||||
|
" displayName=" + displayName + " wakelockTimeMs=" + wakelockTimeMs +
|
||||||
|
" wakeupAlarmCount=" + wakeupAlarmCount + " bluetoothTimeMs="
|
||||||
|
+ bluetoothScanningTimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toAnomalyTypeText(@AnomalyType int type) {
|
||||||
|
switch (type) {
|
||||||
|
case AnomalyType.WAKEUP_ALARM:
|
||||||
|
return "wakeupAlarm";
|
||||||
|
case AnomalyType.WAKE_LOCK:
|
||||||
|
return "wakelock";
|
||||||
|
case AnomalyType.BLUETOOTH_SCAN:
|
||||||
|
return "unoptimizedBluetoothScan";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
|
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
|
||||||
@@ -160,6 +191,8 @@ public class Anomaly implements Parcelable {
|
|||||||
private String mPackageName;
|
private String mPackageName;
|
||||||
private long mWakeLockTimeMs;
|
private long mWakeLockTimeMs;
|
||||||
private boolean mBgRestrictionEnabled;
|
private boolean mBgRestrictionEnabled;
|
||||||
|
private int mWakeupAlarmCount;
|
||||||
|
private long mBluetoothScanningTimeMs;
|
||||||
|
|
||||||
public Builder setType(@AnomalyType int type) {
|
public Builder setType(@AnomalyType int type) {
|
||||||
mType = type;
|
mType = type;
|
||||||
@@ -196,6 +229,16 @@ public class Anomaly implements Parcelable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder setWakeupAlarmCount(int wakeupAlarmCount) {
|
||||||
|
mWakeupAlarmCount = wakeupAlarmCount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setBluetoothScanningTimeMs(long bluetoothScanningTimeMs) {
|
||||||
|
mBluetoothScanningTimeMs = bluetoothScanningTimeMs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Anomaly build() {
|
public Anomaly build() {
|
||||||
return new Anomaly(this);
|
return new Anomaly(this);
|
||||||
}
|
}
|
||||||
|
@@ -25,8 +25,11 @@ import android.support.annotation.VisibleForTesting;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.internal.os.BatteryStatsHelper;
|
import com.android.internal.os.BatteryStatsHelper;
|
||||||
|
import com.android.internal.util.ArrayUtils;
|
||||||
import com.android.settings.utils.AsyncLoader;
|
import com.android.settings.utils.AsyncLoader;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@@ -93,6 +93,7 @@ public class BluetoothScanAnomalyDetector implements AnomalyDetector {
|
|||||||
.setType(Anomaly.AnomalyType.BLUETOOTH_SCAN)
|
.setType(Anomaly.AnomalyType.BLUETOOTH_SCAN)
|
||||||
.setDisplayName(displayName)
|
.setDisplayName(displayName)
|
||||||
.setPackageName(packageName)
|
.setPackageName(packageName)
|
||||||
|
.setBluetoothScanningTimeMs(bluetoothTimeMs)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if (mAnomalyUtils.getAnomalyAction(anomaly).isActionActive(anomaly)) {
|
if (mAnomalyUtils.getAnomalyAction(anomaly).isActionActive(anomaly)) {
|
||||||
|
@@ -97,6 +97,7 @@ public class WakeLockAnomalyDetector implements AnomalyDetector {
|
|||||||
.setType(Anomaly.AnomalyType.WAKE_LOCK)
|
.setType(Anomaly.AnomalyType.WAKE_LOCK)
|
||||||
.setDisplayName(displayName)
|
.setDisplayName(displayName)
|
||||||
.setPackageName(packageName)
|
.setPackageName(packageName)
|
||||||
|
.setWakeLockTimeMs(backgroundDurationMs)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if (mAnomalyUtils.getAnomalyAction(anomaly).isActionActive(anomaly)) {
|
if (mAnomalyUtils.getAnomalyAction(anomaly).isActionActive(anomaly)) {
|
||||||
|
@@ -84,8 +84,9 @@ public class WakeupAlarmAnomalyDetector implements AnomalyDetector {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int wakeups = getWakeupAlarmCountFromUid(uid);
|
final int wakeupAlarmCount = (int) (getWakeupAlarmCountFromUid(uid)
|
||||||
if ((wakeups / totalRunningHours) > mWakeupAlarmThreshold) {
|
/ totalRunningHours);
|
||||||
|
if (wakeupAlarmCount > mWakeupAlarmThreshold) {
|
||||||
final String packageName = mBatteryUtils.getPackageName(uid.getUid());
|
final String packageName = mBatteryUtils.getPackageName(uid.getUid());
|
||||||
final CharSequence displayName = Utils.getApplicationLabel(mContext,
|
final CharSequence displayName = Utils.getApplicationLabel(mContext,
|
||||||
packageName);
|
packageName);
|
||||||
@@ -100,6 +101,7 @@ public class WakeupAlarmAnomalyDetector implements AnomalyDetector {
|
|||||||
.setBackgroundRestrictionEnabled(
|
.setBackgroundRestrictionEnabled(
|
||||||
mBatteryUtils.isBackgroundRestrictionEnabled(targetSdkVersion,
|
mBatteryUtils.isBackgroundRestrictionEnabled(targetSdkVersion,
|
||||||
uid.getUid(), packageName))
|
uid.getUid(), packageName))
|
||||||
|
.setWakeupAlarmCount(wakeupAlarmCount)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if (mAnomalyUtils.getAnomalyAction(anomaly).isActionActive(anomaly)) {
|
if (mAnomalyUtils.getAnomalyAction(anomaly).isActionActive(anomaly)) {
|
||||||
|
@@ -23,6 +23,7 @@ import android.os.Build;
|
|||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
||||||
import com.android.settings.TestConfig;
|
import com.android.settings.TestConfig;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
@@ -36,10 +37,14 @@ public class AnomalyTest {
|
|||||||
private static long WAKE_LOCK_TIME_MS = 1500;
|
private static long WAKE_LOCK_TIME_MS = 1500;
|
||||||
private static String PACKAGE_NAME = "com.android.settings";
|
private static String PACKAGE_NAME = "com.android.settings";
|
||||||
private static String DISPLAY_NAME = "settings";
|
private static String DISPLAY_NAME = "settings";
|
||||||
|
private static long BLUETOOTH_TIME_MS = 2555555;
|
||||||
|
private static int WAKEUP_ALARM_COUNT = 100;
|
||||||
|
|
||||||
@Test
|
private Anomaly mAnomaly;
|
||||||
public void testBuilder_buildCorrectly() {
|
|
||||||
Anomaly anomaly = new Anomaly.Builder()
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mAnomaly = new Anomaly.Builder()
|
||||||
.setType(TYPE)
|
.setType(TYPE)
|
||||||
.setUid(UID)
|
.setUid(UID)
|
||||||
.setWakeLockTimeMs(WAKE_LOCK_TIME_MS)
|
.setWakeLockTimeMs(WAKE_LOCK_TIME_MS)
|
||||||
@@ -47,14 +52,28 @@ public class AnomalyTest {
|
|||||||
.setDisplayName(DISPLAY_NAME)
|
.setDisplayName(DISPLAY_NAME)
|
||||||
.setTargetSdkVersion(SDK_VERSION)
|
.setTargetSdkVersion(SDK_VERSION)
|
||||||
.setBackgroundRestrictionEnabled(true)
|
.setBackgroundRestrictionEnabled(true)
|
||||||
|
.setBluetoothScanningTimeMs(BLUETOOTH_TIME_MS)
|
||||||
|
.setWakeupAlarmCount(WAKEUP_ALARM_COUNT)
|
||||||
.build();
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(anomaly.type).isEqualTo(TYPE);
|
@Test
|
||||||
assertThat(anomaly.uid).isEqualTo(UID);
|
public void testBuilder_buildCorrectly() {
|
||||||
assertThat(anomaly.wakelockTimeMs).isEqualTo(WAKE_LOCK_TIME_MS);
|
assertThat(mAnomaly.type).isEqualTo(TYPE);
|
||||||
assertThat(anomaly.packageName).isEqualTo(PACKAGE_NAME);
|
assertThat(mAnomaly.uid).isEqualTo(UID);
|
||||||
assertThat(anomaly.displayName).isEqualTo(DISPLAY_NAME);
|
assertThat(mAnomaly.wakelockTimeMs).isEqualTo(WAKE_LOCK_TIME_MS);
|
||||||
assertThat(anomaly.targetSdkVersion).isEqualTo(SDK_VERSION);
|
assertThat(mAnomaly.packageName).isEqualTo(PACKAGE_NAME);
|
||||||
assertThat(anomaly.backgroundRestrictionEnabled).isTrue();
|
assertThat(mAnomaly.displayName).isEqualTo(DISPLAY_NAME);
|
||||||
|
assertThat(mAnomaly.targetSdkVersion).isEqualTo(SDK_VERSION);
|
||||||
|
assertThat(mAnomaly.backgroundRestrictionEnabled).isTrue();
|
||||||
|
assertThat(mAnomaly.wakeupAlarmCount).isEqualTo(WAKEUP_ALARM_COUNT);
|
||||||
|
assertThat(mAnomaly.bluetoothScanningTimeMs).isEqualTo(BLUETOOTH_TIME_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToString() {
|
||||||
|
assertThat(mAnomaly.toString()).isEqualTo(
|
||||||
|
"type=wakelock uid=111 package=com.android.settings displayName=settings"
|
||||||
|
+ " wakelockTimeMs=1500 wakeupAlarmCount=100 bluetoothTimeMs=2555555");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -155,6 +155,7 @@ public class BluetoothScanAnomalyDetectorTest {
|
|||||||
return new Anomaly.Builder()
|
return new Anomaly.Builder()
|
||||||
.setUid(uid)
|
.setUid(uid)
|
||||||
.setType(Anomaly.AnomalyType.BLUETOOTH_SCAN)
|
.setType(Anomaly.AnomalyType.BLUETOOTH_SCAN)
|
||||||
|
.setBluetoothScanningTimeMs(ANOMALY_BLUETOOTH_SCANNING_TIME)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -99,6 +99,8 @@ public class WakeLockAnomalyDetectorTest {
|
|||||||
private WakeLockAnomalyDetector mWakelockAnomalyDetector;
|
private WakeLockAnomalyDetector mWakelockAnomalyDetector;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private List<BatterySipper> mUsageList;
|
private List<BatterySipper> mUsageList;
|
||||||
|
private Anomaly mAnomaly;
|
||||||
|
private Anomaly mTargetAnomaly;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
@@ -152,41 +154,40 @@ public class WakeLockAnomalyDetectorTest {
|
|||||||
mUsageList.add(mTargetSipper);
|
mUsageList.add(mTargetSipper);
|
||||||
mUsageList.add(mInactiveSipper);
|
mUsageList.add(mInactiveSipper);
|
||||||
doReturn(mUsageList).when(mBatteryStatsHelper).getUsageList();
|
doReturn(mUsageList).when(mBatteryStatsHelper).getUsageList();
|
||||||
|
|
||||||
|
mAnomaly = createWakeLockAnomaly(ANOMALY_UID);
|
||||||
|
mTargetAnomaly = createWakeLockAnomaly(TARGET_UID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDetectAnomalies_containsAnomaly_detectIt() {
|
public void testDetectAnomalies_containsAnomaly_detectIt() {
|
||||||
doReturn(BatteryUtils.UID_NULL).when(mBatteryUtils).getPackageUid(nullable(String.class));
|
doReturn(BatteryUtils.UID_NULL).when(mBatteryUtils).getPackageUid(nullable(String.class));
|
||||||
final Anomaly anomaly = new Anomaly.Builder()
|
|
||||||
.setUid(ANOMALY_UID)
|
|
||||||
.setType(Anomaly.AnomalyType.WAKE_LOCK)
|
|
||||||
.build();
|
|
||||||
final Anomaly targetAnomaly = new Anomaly.Builder()
|
|
||||||
.setUid(TARGET_UID)
|
|
||||||
.setType(Anomaly.AnomalyType.WAKE_LOCK)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
List<Anomaly> mAnomalies = mWakelockAnomalyDetector.detectAnomalies(mBatteryStatsHelper);
|
List<Anomaly> mAnomalies = mWakelockAnomalyDetector.detectAnomalies(mBatteryStatsHelper);
|
||||||
|
|
||||||
assertThat(mAnomalies).containsExactly(anomaly, targetAnomaly);
|
assertThat(mAnomalies).containsExactly(mAnomaly, mTargetAnomaly);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDetectAnomalies_containsTargetPackage_detectIt() {
|
public void testDetectAnomalies_containsTargetPackage_detectIt() {
|
||||||
doReturn(TARGET_UID).when(mBatteryUtils).getPackageUid(TARGET_PACKAGE_NAME);
|
doReturn(TARGET_UID).when(mBatteryUtils).getPackageUid(TARGET_PACKAGE_NAME);
|
||||||
final Anomaly targetAnomaly = new Anomaly.Builder()
|
|
||||||
.setUid(TARGET_UID)
|
|
||||||
.setType(Anomaly.AnomalyType.WAKE_LOCK)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
List<Anomaly> mAnomalies = mWakelockAnomalyDetector.detectAnomalies(mBatteryStatsHelper,
|
List<Anomaly> mAnomalies = mWakelockAnomalyDetector.detectAnomalies(mBatteryStatsHelper,
|
||||||
TARGET_PACKAGE_NAME);
|
TARGET_PACKAGE_NAME);
|
||||||
|
|
||||||
assertThat(mAnomalies).containsExactly(targetAnomaly);
|
assertThat(mAnomalies).containsExactly(mTargetAnomaly);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testContainsThresholdFromPolicy() {
|
public void testContainsThresholdFromPolicy() {
|
||||||
assertThat(mWakelockAnomalyDetector.mWakeLockThresholdMs).isEqualTo(WAKELOCK_THRESHOLD_MS);
|
assertThat(mWakelockAnomalyDetector.mWakeLockThresholdMs).isEqualTo(WAKELOCK_THRESHOLD_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Anomaly createWakeLockAnomaly(int uid) {
|
||||||
|
return new Anomaly.Builder()
|
||||||
|
.setUid(uid)
|
||||||
|
.setType(Anomaly.AnomalyType.WAKE_LOCK)
|
||||||
|
.setWakeLockTimeMs(ANOMALY_WAKELOCK_TIME_MS)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -69,6 +69,7 @@ public class WakeupAlarmAnomalyDetectorTest {
|
|||||||
1 * DateUtils.HOUR_IN_MILLIS + 10 * DateUtils.MINUTE_IN_MILLIS;
|
1 * DateUtils.HOUR_IN_MILLIS + 10 * DateUtils.MINUTE_IN_MILLIS;
|
||||||
private static final int ANOMALY_WAKEUP_COUNT = 500;
|
private static final int ANOMALY_WAKEUP_COUNT = 500;
|
||||||
private static final int NORMAL_WAKEUP_COUNT = 61;
|
private static final int NORMAL_WAKEUP_COUNT = 61;
|
||||||
|
private static final int ANOMALY_WAKEUP_FREQUENCY = 428; // count per hour
|
||||||
@Mock
|
@Mock
|
||||||
private BatteryStatsHelper mBatteryStatsHelper;
|
private BatteryStatsHelper mBatteryStatsHelper;
|
||||||
@Mock
|
@Mock
|
||||||
@@ -145,6 +146,7 @@ public class WakeupAlarmAnomalyDetectorTest {
|
|||||||
.setType(Anomaly.AnomalyType.WAKEUP_ALARM)
|
.setType(Anomaly.AnomalyType.WAKEUP_ALARM)
|
||||||
.setTargetSdkVersion(ANOMALY_SDK)
|
.setTargetSdkVersion(ANOMALY_SDK)
|
||||||
.setBackgroundRestrictionEnabled(ANOMALY_BACKGROUND_RESTRICTION_ON)
|
.setBackgroundRestrictionEnabled(ANOMALY_BACKGROUND_RESTRICTION_ON)
|
||||||
|
.setWakeupAlarmCount(ANOMALY_WAKEUP_FREQUENCY)
|
||||||
.build();
|
.build();
|
||||||
mTargetAnomaly = new Anomaly.Builder()
|
mTargetAnomaly = new Anomaly.Builder()
|
||||||
.setUid(TARGET_UID)
|
.setUid(TARGET_UID)
|
||||||
@@ -152,6 +154,7 @@ public class WakeupAlarmAnomalyDetectorTest {
|
|||||||
.setType(Anomaly.AnomalyType.WAKEUP_ALARM)
|
.setType(Anomaly.AnomalyType.WAKEUP_ALARM)
|
||||||
.setTargetSdkVersion(TARGET_SDK)
|
.setTargetSdkVersion(TARGET_SDK)
|
||||||
.setBackgroundRestrictionEnabled(TARGET_BACKGROUND_RESTRICTION_ON)
|
.setBackgroundRestrictionEnabled(TARGET_BACKGROUND_RESTRICTION_ON)
|
||||||
|
.setWakeupAlarmCount(ANOMALY_WAKEUP_FREQUENCY)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
mWakeupAlarmAnomalyDetector = spy(
|
mWakeupAlarmAnomalyDetector = spy(
|
||||||
|
Reference in New Issue
Block a user