mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-26 02:25:50 +01:00
Implement factory reset feature in debug activity
Implemented for Mi Band 1/2/3, Cor, Bip Could be implemented for Pebble by deleting all blobdbs etc Related to #109
This commit is contained in:
parent
4bd79f8058
commit
b020d59f54
@ -28,7 +28,6 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
@ -59,6 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
import static android.content.Intent.EXTRA_SUBJECT;
|
||||
@ -183,9 +183,33 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
rebootButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
GBApplication.deviceService().onReboot();
|
||||
GBApplication.deviceService().onReset(GBDeviceProtocol.RESET_FLAGS_REBOOT);
|
||||
}
|
||||
});
|
||||
|
||||
Button factoryResetButton = findViewById(R.id.factoryResetButton);
|
||||
factoryResetButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new AlertDialog.Builder(DebugActivity.this)
|
||||
.setCancelable(true)
|
||||
.setTitle(R.string.debugactivity_really_factoryreset_title)
|
||||
.setMessage(R.string.debugactivity_really_factoryreset)
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
GBApplication.deviceService().onReset(GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
});
|
||||
|
||||
Button heartRateButton = findViewById(R.id.HeartRateButton);
|
||||
heartRateButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -278,14 +302,7 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String fileName = GBApplication.getLogPath();
|
||||
if (fileName != null && fileName.length() > 0) {
|
||||
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
|
||||
emailIntent.setType("*/*");
|
||||
emailIntent.putExtra(EXTRA_SUBJECT, "Gadgetbridge log file");
|
||||
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(fileName)));
|
||||
startActivity(Intent.createChooser(emailIntent, "Share File"));
|
||||
}
|
||||
shareLog();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
|
||||
|
@ -69,7 +69,7 @@ public interface EventHandler {
|
||||
|
||||
void onFetchRecordedData(int dataTypes);
|
||||
|
||||
void onReboot();
|
||||
void onReset(int flags);
|
||||
|
||||
void onHeartRateTest();
|
||||
|
||||
|
@ -149,6 +149,7 @@ public class HuamiService {
|
||||
public static final byte[] COMMAND_DISTANCE_UNIT_METRIC = new byte[] { ENDPOINT_DISPLAY, 0x03, 0x00, 0x00 };
|
||||
public static final byte[] COMMAND_DISTANCE_UNIT_IMPERIAL = new byte[] { ENDPOINT_DISPLAY, 0x03, 0x00, 0x01 };
|
||||
public static final byte[] COMMAND_SET_LANGUAGE_NEW_TEMPLATE = new byte[]{ENDPOINT_DISPLAY, 0x17, 0x00, 0, 0, 0, 0, 0};
|
||||
public static final byte[] COMMAND_FACTORY_RESET = new byte[]{ENDPOINT_DISPLAY, 0x0b, 0x00, 0x01};
|
||||
|
||||
// The third byte controls the threshold, in minutes
|
||||
// The last 8 bytes represent 2 separate time intervals for the inactivity warnings
|
||||
|
@ -163,6 +163,8 @@ public class MiBandService {
|
||||
|
||||
public static final byte COMMAND_SEND_NOTIFICATION = 0x8;
|
||||
|
||||
public static final byte COMMAND_FACTORYRESET = 0x9;
|
||||
|
||||
public static final byte COMMAND_CONFIRM_ACTIVITY_DATA_TRANSFER_COMPLETE = 0xa;
|
||||
|
||||
public static final byte COMMAND_SYNC = 0xb;
|
||||
|
@ -291,8 +291,9 @@ public class GBDeviceService implements DeviceService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
Intent intent = createIntent().setAction(ACTION_REBOOT);
|
||||
public void onReset(int flags) {
|
||||
Intent intent = createIntent().setAction(ACTION_RESET)
|
||||
.putExtra(EXTRA_RESET_FLAGS, flags);
|
||||
invokeService(intent);
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
|
||||
public interface DeviceService extends EventHandler {
|
||||
String PREFIX = "nodomain.freeyourgadget.gadgetbridge.devices";
|
||||
|
||||
String ACTION_MIBAND2_AUTH = PREFIX + ".action.miban2_auth";
|
||||
String ACTION_START = PREFIX + ".action.start";
|
||||
String ACTION_CONNECT = PREFIX + ".action.connect";
|
||||
String ACTION_NOTIFICATION = PREFIX + ".action.notification";
|
||||
@ -47,7 +46,7 @@ public interface DeviceService extends EventHandler {
|
||||
String ACTION_APP_CONFIGURE = PREFIX + ".action.app_configure";
|
||||
String ACTION_APP_REORDER = PREFIX + ".action.app_reorder";
|
||||
String ACTION_INSTALL = PREFIX + ".action.install";
|
||||
String ACTION_REBOOT = PREFIX + ".action.reboot";
|
||||
String ACTION_RESET = PREFIX + ".action.reset";
|
||||
String ACTION_HEARTRATE_TEST = PREFIX + ".action.heartrate_test";
|
||||
String ACTION_FETCH_RECORDED_DATA = PREFIX + ".action.fetch_activity_data";
|
||||
String ACTION_DISCONNECT = PREFIX + ".action.disconnect";
|
||||
@ -110,6 +109,7 @@ public interface DeviceService extends EventHandler {
|
||||
String EXTRA_RECORDED_DATA_TYPES = "data_types";
|
||||
String EXTRA_FM_FREQUENCY = "fm_frequency";
|
||||
String EXTRA_LED_COLOR = "led_color";
|
||||
String EXTRA_RESET_FLAGS = "reset_flags";
|
||||
|
||||
/**
|
||||
* Use EXTRA_REALTIME_SAMPLE instead
|
||||
|
@ -94,7 +94,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FI
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_HEARTRATE_TEST;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_INSTALL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REBOOT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_RESET;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_APPINFO;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_DEVICEINFO;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_SCREENSHOT;
|
||||
@ -160,6 +160,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOT
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TITLE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TYPE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_RECORDED_DATA_TYPES;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_RESET_FLAGS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_VIBRATION_INTENSITY;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER;
|
||||
@ -416,8 +417,9 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
mDeviceSupport.onDeleteCalendarEvent(type, id);
|
||||
break;
|
||||
}
|
||||
case ACTION_REBOOT: {
|
||||
mDeviceSupport.onReboot();
|
||||
case ACTION_RESET: {
|
||||
int flags = intent.getIntExtra(EXTRA_RESET_FLAGS, 0);
|
||||
mDeviceSupport.onReset(flags);
|
||||
break;
|
||||
}
|
||||
case ACTION_HEARTRATE_TEST: {
|
||||
|
@ -256,11 +256,11 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
if (checkBusy("reboot")) {
|
||||
public void onReset(int flags) {
|
||||
if (checkBusy("reset")) {
|
||||
return;
|
||||
}
|
||||
delegate.onReboot();
|
||||
delegate.onReset(flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -526,7 +526,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
public void onReset(int flags) {
|
||||
try {
|
||||
getQueue().clear();
|
||||
|
||||
|
@ -112,6 +112,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.Ini
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.RealtimeSamplesSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
|
||||
@ -829,13 +830,17 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
public void onReset(int flags) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Reboot");
|
||||
sendReboot(builder);
|
||||
TransactionBuilder builder = performInitialized("Reset");
|
||||
if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) != 0) {
|
||||
sendFactoryReset(builder);
|
||||
} else {
|
||||
sendReboot(builder);
|
||||
}
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to reboot MI", ex);
|
||||
LOG.error("Unable to reset", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -844,6 +849,11 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
public HuamiSupport sendFactoryReset(TransactionBuilder builder) {
|
||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_FACTORY_RESET);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeartRateTest() {
|
||||
if (characteristicHRControlPoint == null) {
|
||||
|
@ -185,7 +185,7 @@ public class ID115Support extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
public void onReset(int flags) {
|
||||
try {
|
||||
getQueue().clear();
|
||||
|
||||
|
@ -367,7 +367,7 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
public void onReset(int flags) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Reboot");
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
|
@ -86,6 +86,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotificat
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.FetchActivityOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
|
||||
@ -270,6 +271,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
static final byte[] reboot = new byte[]{MiBandService.COMMAND_REBOOT};
|
||||
static final byte[] factoryReset = new byte[]{MiBandService.COMMAND_FACTORYRESET};
|
||||
|
||||
static final byte[] startHeartMeasurementManual = new byte[]{0x15, MiBandService.COMMAND_SET_HR_MANUAL, 1};
|
||||
static final byte[] stopHeartMeasurementManual = new byte[]{0x15, MiBandService.COMMAND_SET_HR_MANUAL, 0};
|
||||
@ -689,13 +691,17 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
public void onReset(int flags) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Reboot");
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), reboot);
|
||||
TransactionBuilder builder = performInitialized("reset");
|
||||
if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) != 0) {
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), factoryReset);
|
||||
} else {
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), reboot);
|
||||
}
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to reboot MI", ex);
|
||||
LOG.error("Unable to reset", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAc
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.AbstractMiFirmwareInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
@ -155,7 +156,7 @@ public class UpdateFirmwareOperation extends AbstractMiBand1Operation {
|
||||
} else if (updateCoordinator.needsReboot()) {
|
||||
displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_update_complete_rebooting), Toast.LENGTH_LONG, GB.INFO);
|
||||
GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext());
|
||||
getSupport().onReboot();
|
||||
getSupport().onReset(GBDeviceProtocol.RESET_FLAGS_REBOOT);
|
||||
} else {
|
||||
LOG.error("BUG: Successful firmware update without reboot???");
|
||||
}
|
||||
|
@ -296,7 +296,7 @@ public class No1F1Support extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
public void onReset(int flags) {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1468,7 +1468,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encodeReboot() {
|
||||
public byte[] encodeReset(int flags) {
|
||||
return encodeSimpleMessage(ENDPOINT_RESET, RESET_REBOOT);
|
||||
}
|
||||
|
||||
|
@ -197,7 +197,7 @@ public class VibratissimoSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
public void onReset(int flags) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -440,7 +440,7 @@ public class Watch9DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
public void onReset(int flags) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -233,7 +233,7 @@ public class XWatchSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
public void onReset(int flags) {
|
||||
//Not supported
|
||||
}
|
||||
|
||||
|
@ -411,7 +411,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
public void onReset(int flags) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -186,8 +186,8 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
byte[] bytes = gbDeviceProtocol.encodeReboot();
|
||||
public void onReset(int flags) {
|
||||
byte[] bytes = gbDeviceProtocol.encodeReset(flags);
|
||||
sendToDevice(bytes);
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,9 @@ import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
|
||||
public abstract class GBDeviceProtocol {
|
||||
|
||||
public static final int RESET_FLAGS_REBOOT = 1;
|
||||
public static final int RESET_FLAGS_FACTORY_RESET = 2;
|
||||
|
||||
private GBDevice mDevice;
|
||||
|
||||
protected GBDeviceProtocol(GBDevice device) {
|
||||
@ -90,7 +93,7 @@ public abstract class GBDeviceProtocol {
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] encodeReboot() {
|
||||
public byte[] encodeReset(int flags) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,14 @@
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="reboot" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/factoryResetButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="factory reset" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/testNotificationButton"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -26,7 +26,10 @@
|
||||
<string name="controlcenter_calibrate_device">Calibrate Device</string>
|
||||
|
||||
|
||||
<!-- Strings related to Debug Activity -->
|
||||
<string name="title_activity_debug">Debug</string>
|
||||
<string name="debugactivity_really_factoryreset_title">Really factory reset?</string>
|
||||
<string name="debugactivity_really_factoryreset">Doing a factory reset will delete all data from the connected device (if supported). In case of Xiaomi/Huami devices your Bluetooth MAC address will change, so it will appear as a new device to Gadgetbrige"</string>
|
||||
|
||||
<!-- Strings related to AppManager -->
|
||||
<string name="title_activity_appmanager">App Manager</string>
|
||||
@ -109,7 +112,7 @@
|
||||
<string name="pref_title_rtl">Right-To-Left</string>
|
||||
<string name="pref_summary_rtl">Enable this if your device can not show right-to-left languages</string>
|
||||
<string name="pref_rtl_max_line_length">Right-To-Left Max Line Length</string>
|
||||
<string name="pref_rtl_max_line_length_summary">Lengthens or shortens the lines Right-To-Left text is seperated into</string>
|
||||
<string name="pref_rtl_max_line_length_summary">Lengthens or shortens the lines Right-To-Left text is separated into</string>
|
||||
|
||||
<string name="always">Always</string>
|
||||
<string name="when_screen_off">When screen is off</string>
|
||||
|
@ -120,7 +120,7 @@ class TestDeviceSupport extends AbstractDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
public void onReset(int flags) {
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user