Wifi tethering without root above Oreo works again.
This commit is contained in:
parent
7fd8d1cfd0
commit
7182698b8a
@ -20,9 +20,12 @@ import android.telephony.SmsManager;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.jens.automation2.actions.wifi_router.MyOnStartTetheringCallback;
|
||||
import com.jens.automation2.actions.wifi_router.MyOreoWifiManager;
|
||||
import com.jens.automation2.location.WifiBroadcastReceiver;
|
||||
import com.jens.automation2.receivers.ConnectivityReceiver;
|
||||
|
||||
@ -190,44 +193,70 @@ public class Actions
|
||||
|
||||
if(((state && !desiredState) || (!state && desiredState)))
|
||||
{
|
||||
WifiManager wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
|
||||
Method[] methods = wifiManager.getClass().getDeclaredMethods();
|
||||
for(Method method : methods)
|
||||
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||
{
|
||||
Miscellaneous.logEvent("i", "WifiAp", "Trying to find appropriate method... " + method.getName(), 5);
|
||||
if(method.getName().equals("setWifiApEnabled"))
|
||||
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
Method[] methods = wifiManager.getClass().getDeclaredMethods();
|
||||
for (Method method : methods)
|
||||
{
|
||||
try
|
||||
Miscellaneous.logEvent("i", "WifiAp", "Trying to find appropriate method... " + method.getName(), 5);
|
||||
if (method.getName().equals("setWifiApEnabled"))
|
||||
{
|
||||
String desiredString = "";
|
||||
if(desiredState)
|
||||
desiredString = "activate";
|
||||
else
|
||||
desiredString = "deactivate";
|
||||
|
||||
if(!toggleActionIfPossible)
|
||||
try
|
||||
{
|
||||
Miscellaneous.logEvent("i", "WifiAp", "Trying to " + desiredString + " wifi ap...", 2);
|
||||
if(!method.isAccessible())
|
||||
method.setAccessible(true);
|
||||
method.invoke(wifiManager, null, desiredState);
|
||||
}
|
||||
else
|
||||
{
|
||||
Miscellaneous.logEvent("i", "WifiAp", "Trying to " + context.getResources().getString(R.string.toggle) + " wifi ap...", 2);
|
||||
if(!method.isAccessible())
|
||||
method.setAccessible(true);
|
||||
method.invoke(wifiManager, null, !state);
|
||||
}
|
||||
String desiredString = "";
|
||||
if (desiredState)
|
||||
desiredString = "activate";
|
||||
else
|
||||
desiredString = "deactivate";
|
||||
|
||||
Miscellaneous.logEvent("i", "WifiAp", "Wifi ap " + desiredString + "d.", 2);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Miscellaneous.logEvent("i", "WifiAp", context.getResources().getString(R.string.errorActivatingWifiAp) + ". " + e.getMessage(), 2);
|
||||
if (!toggleActionIfPossible)
|
||||
{
|
||||
Miscellaneous.logEvent("i", "WifiAp", "Trying to " + desiredString + " wifi ap...", 2);
|
||||
if (!method.isAccessible())
|
||||
method.setAccessible(true);
|
||||
method.invoke(wifiManager, null, desiredState);
|
||||
}
|
||||
else
|
||||
{
|
||||
Miscellaneous.logEvent("i", "WifiAp", "Trying to " + context.getResources().getString(R.string.toggle) + " wifi ap...", 2);
|
||||
if (!method.isAccessible())
|
||||
method.setAccessible(true);
|
||||
method.invoke(wifiManager, null, !state);
|
||||
}
|
||||
|
||||
Miscellaneous.logEvent("i", "WifiAp", "Wifi ap " + desiredString + "d.", 2);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Miscellaneous.logEvent("i", "WifiAp", context.getResources().getString(R.string.errorActivatingWifiAp) + ". " + e.getMessage(), 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MyOnStartTetheringCallback cb = new MyOnStartTetheringCallback()
|
||||
{
|
||||
@Override
|
||||
public void onTetheringStarted()
|
||||
{
|
||||
Log.i("Tether", "Läuft");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTetheringFailed()
|
||||
{
|
||||
Log.i("Tether", "Doof");
|
||||
}
|
||||
};
|
||||
|
||||
MyOreoWifiManager mowm = new MyOreoWifiManager(context);
|
||||
if(desiredState)
|
||||
mowm.startTethering(cb);
|
||||
else
|
||||
mowm.stopTethering();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1,255 +0,0 @@
|
||||
package com.jens.automation2.actions.wifi_router;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.IntentService;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static android.content.ContentValues.TAG;
|
||||
|
||||
/**
|
||||
* An {@link IntentService} subclass for handling asynchronous task requests in
|
||||
* a service on a separate handler thread.
|
||||
*/
|
||||
public class HotSpotIntentService extends IntentService
|
||||
{
|
||||
/**
|
||||
Id for running service in foreground
|
||||
*/
|
||||
private static int FOREGROUND_ID=1338;
|
||||
private static final String CHANNEL_ID = "control_app";
|
||||
|
||||
// Action names...assigned in manifest.
|
||||
private String ACTION_TURNON;
|
||||
private String ACTION_TURNOFF;
|
||||
private String DATAURI_TURNON;
|
||||
private String DATAURI_TURNOFF;
|
||||
private Intent mStartIntent;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
MyOreoWifiManager mMyOreoWifiManager;
|
||||
|
||||
|
||||
/**
|
||||
* Creates an IntentService. Invoked by your subclass's constructor.
|
||||
*
|
||||
|
||||
*/
|
||||
public HotSpotIntentService()
|
||||
{
|
||||
super("HotSpotIntentService");
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to start this intent from {@link HotSpotIntentReceiver}
|
||||
* @param context
|
||||
* @param intent
|
||||
*/
|
||||
public static void start(Context context,Intent intent)
|
||||
{
|
||||
Intent i = new Intent(context, HotSpotIntentService.class);
|
||||
i.setAction(intent.getAction());
|
||||
i.setData(intent.getData());
|
||||
context.startService(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(@Nullable Intent intent)
|
||||
{
|
||||
ACTION_TURNON = getString(R.string.intent_action_turnon);
|
||||
ACTION_TURNOFF = getString(R.string.intent_action_turnoff);
|
||||
|
||||
DATAURI_TURNON = getString(R.string.intent_data_host_turnon);
|
||||
DATAURI_TURNOFF = getString(R.string.intent_data_host_turnoff);
|
||||
|
||||
// Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
|
||||
Log.i(TAG,"Received start intent");
|
||||
|
||||
mStartIntent = intent;
|
||||
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
carryOn();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void carryOn()
|
||||
{
|
||||
boolean turnOn = true;
|
||||
if (mStartIntent != null)
|
||||
{
|
||||
final String action = mStartIntent.getAction();
|
||||
final String data = mStartIntent.getDataString();
|
||||
if (ACTION_TURNON.equals(action) || (data!=null && data.contains(DATAURI_TURNON)))
|
||||
{
|
||||
turnOn = true;
|
||||
Log.i(TAG,"Action/data to turn on hotspot");
|
||||
}
|
||||
else if (ACTION_TURNOFF.equals(action)|| (data!=null && data.contains(DATAURI_TURNOFF)))
|
||||
{
|
||||
turnOn = false;
|
||||
Log.i(TAG,"Action/data to turn off hotspot");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O)
|
||||
{
|
||||
hotspotOreo(turnOn);
|
||||
}
|
||||
else
|
||||
{
|
||||
turnOnHotspotPreOreo(turnOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean turnOnHotspotPreOreo(boolean turnOn)
|
||||
{
|
||||
{
|
||||
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
|
||||
Method[] methods = wifiManager.getClass().getDeclaredMethods();
|
||||
for (Method method : methods)
|
||||
{
|
||||
if (method.getName().equals("setWifiApEnabled"))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (turnOn)
|
||||
{
|
||||
wifiManager.setWifiEnabled(false); //Turning off wifi because tethering requires wifi to be off
|
||||
method.invoke(wifiManager, null, true); //Activating tethering
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
method.invoke(wifiManager, null, false); //Deactivating tethering
|
||||
wifiManager.setWifiEnabled(true); //Turning on wifi ...should probably be done from a saved setting
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Error setWifiApEnabled not found
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private void hotspotOreo(boolean turnOn){
|
||||
|
||||
if (mMyOreoWifiManager ==null)
|
||||
{
|
||||
mMyOreoWifiManager = new MyOreoWifiManager(this);
|
||||
}
|
||||
|
||||
if (turnOn)
|
||||
{
|
||||
//this dont work
|
||||
MyOnStartTetheringCallback callback = new MyOnStartTetheringCallback()
|
||||
{
|
||||
@Override
|
||||
public void onTetheringStarted()
|
||||
{
|
||||
startForeground(FOREGROUND_ID, buildForegroundNotification());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTetheringFailed()
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
mMyOreoWifiManager.startTethering(callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
mMyOreoWifiManager.stopTethering();
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
//****************************************************************************************
|
||||
|
||||
|
||||
/**
|
||||
* Build low priority notification for running this service as a foreground service.
|
||||
* @return
|
||||
*/
|
||||
private Notification buildForegroundNotification()
|
||||
{
|
||||
registerNotifChnnl(this);
|
||||
|
||||
Intent stopIntent = new Intent(this, HotSpotIntentService.class);
|
||||
stopIntent.setAction(getString(R.string.intent_action_turnoff));
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getService(this,0, stopIntent, 0);
|
||||
|
||||
NotificationCompat.Builder b=new NotificationCompat.Builder(this,CHANNEL_ID);
|
||||
b.setOngoing(true)
|
||||
.setContentTitle("WifiHotSpot is On")
|
||||
.addAction(new NotificationCompat.Action(
|
||||
R.drawable.turn_off,
|
||||
"TURN OFF HOTSPOT",
|
||||
pendingIntent
|
||||
))
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setCategory(Notification.CATEGORY_SERVICE)
|
||||
.setSmallIcon(R.drawable.notif_hotspot_black_24dp);
|
||||
|
||||
|
||||
return(b.build());
|
||||
}
|
||||
|
||||
private static void registerNotifChnnl(Context context)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= 26)
|
||||
{
|
||||
NotificationManager mngr = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
|
||||
if (mngr.getNotificationChannel(CHANNEL_ID) != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
context.getString(R.string.notification_chnnl),
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
// Configure the notification channel.
|
||||
channel.setDescription(context.getString(R.string.notification_chnnl_location_descr));
|
||||
channel.enableLights(false);
|
||||
channel.enableVibration(false);
|
||||
mngr.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package com.jens.automation2.actions.wifi_router;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class MagicActivity extends Activity
|
||||
{
|
||||
public static void useMagicActivityToTurnOn(Context c)
|
||||
{
|
||||
Uri uri = new Uri.Builder().scheme(c.getString(R.string.intent_data_scheme)).authority(c.getString(R.string.intent_data_host_turnon)).build();
|
||||
Toast.makeText(c,"Turn on. Uri: "+uri.toString(),Toast.LENGTH_LONG).show();
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(uri);
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
c.startActivity(i);
|
||||
}
|
||||
|
||||
public static void useMagicActivityToTurnOff(Context c)
|
||||
{
|
||||
Uri uri = new Uri.Builder().scheme(c.getString(R.string.intent_data_scheme)).authority(c.getString(R.string.intent_data_host_turnoff)).build();
|
||||
Toast.makeText(c,"Turn off. Uri: "+uri.toString(),Toast.LENGTH_LONG).show();
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(uri);
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
c.startActivity(i);
|
||||
}
|
||||
|
||||
private static final String TAG = MagicActivity.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.e(TAG, "onCreate");
|
||||
}
|
||||
|
||||
@Override
|
||||
void onPermissionsOkay()
|
||||
{
|
||||
carryOnWithHotSpotting();
|
||||
}
|
||||
|
||||
/**
|
||||
* The whole purpose of this activity - to start {@link HotSpotIntentService}
|
||||
* This may be called straright away in {@code onCreate} or after permissions granted.
|
||||
*/
|
||||
private void carryOnWithHotSpotting()
|
||||
{
|
||||
Intent intent = getIntent();
|
||||
HotSpotIntentService.start(this, intent);
|
||||
finish();
|
||||
}
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
package com.jens.automation2.actions.wifi_router;
|
||||
|
||||
/*
|
||||
Class taken from here:
|
||||
https://github.com/aegis1980/WifiHotSpot
|
||||
*/
|
||||
|
||||
public abstract class MyOnStartTetheringCallback
|
||||
{
|
||||
/**
|
||||
@ -11,5 +16,4 @@ public abstract class MyOnStartTetheringCallback
|
||||
* Called when starting tethering failed.
|
||||
*/
|
||||
public abstract void onTetheringFailed();
|
||||
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
package com.jens.automation2.actions.wifi_router;
|
||||
|
||||
/*
|
||||
Class taken from here:
|
||||
https://github.com/aegis1980/WifiHotSpot
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
@ -10,6 +15,7 @@ import android.util.Log;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.android.dx.stock.ProxyBuilder;
|
||||
import com.jens.automation2.Miscellaneous;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
@ -51,12 +57,11 @@ public class MyOreoWifiManager
|
||||
{
|
||||
Method setConfigMethod = mWifiManager.getClass().getMethod("setWifiApConfiguration", WifiConfiguration.class);
|
||||
boolean status = (boolean) setConfigMethod.invoke(mWifiManager, apConfig);
|
||||
Log.d(TAG, "setWifiApConfiguration - success? " + status);
|
||||
Miscellaneous.logEvent("i", "configureHotspot()", "setWifiApConfiguration - success? " + status, 2);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(TAG, "Error in configureHotspot");
|
||||
e.printStackTrace();
|
||||
Miscellaneous.logEvent("e", "configureHotspot()", "Error in configureHotspot: " + Log.getStackTraceString(e), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,13 +79,14 @@ public class MyOreoWifiManager
|
||||
Method method = mConnectivityManager.getClass().getDeclaredMethod("getTetheredIfaces");
|
||||
if (method == null)
|
||||
{
|
||||
Log.e(TAG, "getTetheredIfaces is null");
|
||||
Miscellaneous.logEvent("i", "getTetheredIfaces()", "getTetheredIfaces is null", 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
String res[] = (String []) method.invoke(mConnectivityManager, null);
|
||||
Log.d(TAG, "getTetheredIfaces invoked");
|
||||
Log.d(TAG, Arrays.toString(res));
|
||||
Miscellaneous.logEvent("i", "isTetherActive()", "getTetheredIfaces invoked", 5);
|
||||
Miscellaneous.logEvent("i", "isTetherActive()", Arrays.toString(res), 4);
|
||||
|
||||
if (res.length > 0)
|
||||
{
|
||||
return true;
|
||||
@ -89,8 +95,7 @@ public class MyOreoWifiManager
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(TAG, "Error in getTetheredIfaces");
|
||||
e.printStackTrace();
|
||||
Miscellaneous.logEvent("e", "isTetherActive()", "Error in getTetheredIfaces: " + Log.getStackTraceString(e), 2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -102,12 +107,11 @@ public class MyOreoWifiManager
|
||||
*/
|
||||
public boolean startTethering(final MyOnStartTetheringCallback callback)
|
||||
{
|
||||
|
||||
// On Pie if we try to start tethering while it is already on, it will
|
||||
// be disabled. This is needed when startTethering() is called programmatically.
|
||||
if (isTetherActive())
|
||||
{
|
||||
Log.d(TAG, "Tether already active, returning");
|
||||
Miscellaneous.logEvent("i", "startTethering()", "Tether already active, returning", 2);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -139,8 +143,7 @@ public class MyOreoWifiManager
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(TAG, "Error in enableTethering ProxyBuilder");
|
||||
e.printStackTrace();
|
||||
Miscellaneous.logEvent("e", "startTethering()", "Error in enableTethering ProxyBuilder", 2);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -150,19 +153,18 @@ public class MyOreoWifiManager
|
||||
method = mConnectivityManager.getClass().getDeclaredMethod("startTethering", int.class, boolean.class, OnStartTetheringCallbackClass(), Handler.class);
|
||||
if (method == null)
|
||||
{
|
||||
Log.e(TAG, "startTetheringMethod is null");
|
||||
Miscellaneous.logEvent("w", "startTethering()", "startTetheringMethod is null", 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
method.invoke(mConnectivityManager, ConnectivityManager.TYPE_MOBILE, false, proxy, null);
|
||||
Log.d(TAG, "startTethering invoked");
|
||||
Miscellaneous.logEvent("i", "startTethering()", "startTethering invoked", 5);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(TAG, "Error in enableTethering");
|
||||
e.printStackTrace();
|
||||
Miscellaneous.logEvent("w", "startTethering()", "Error in enableTethering: " + Log.getStackTraceString(e), 2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -174,18 +176,17 @@ public class MyOreoWifiManager
|
||||
Method method = mConnectivityManager.getClass().getDeclaredMethod("stopTethering", int.class);
|
||||
if (method == null)
|
||||
{
|
||||
Log.e(TAG, "stopTetheringMethod is null");
|
||||
Miscellaneous.logEvent("w", "stopTethering", "stopTetheringMethod is null", 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
method.invoke(mConnectivityManager, ConnectivityManager.TYPE_MOBILE);
|
||||
Log.d(TAG, "stopTethering invoked");
|
||||
Miscellaneous.logEvent("i", "stopTethering", "stopTethering invoked", 5);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(TAG, "stopTethering error: " + e.toString());
|
||||
e.printStackTrace();
|
||||
Miscellaneous.logEvent("e", "stopTethering", "stopTethering error: " + Log.getStackTraceString(e), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,9 +198,8 @@ public class MyOreoWifiManager
|
||||
}
|
||||
catch (ClassNotFoundException e)
|
||||
{
|
||||
Log.e(TAG, "OnStartTetheringCallbackClass error: " + e.toString());
|
||||
e.printStackTrace();
|
||||
Miscellaneous.logEvent("e", "OnStartTetheringCallbackClass()", "OnStartTetheringCallbackClass error: " + Log.getStackTraceString(e), 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user