mirror of
https://github.com/home-assistant/android
synced 2024-07-09 04:15:51 +00:00
Catch potential play services errors (#4019)
This commit is contained in:
parent
e44cbf0113
commit
1aa56166c7
|
@ -194,7 +194,12 @@ class HighAccuracyLocationService : Service() {
|
|||
.setPriority(Priority.PRIORITY_HIGH_ACCURACY)
|
||||
.build()
|
||||
|
||||
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
|
||||
fusedLocationProviderClient = try {
|
||||
LocationServices.getFusedLocationProviderClient(this)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to get fused location provider client", e)
|
||||
null
|
||||
}
|
||||
fusedLocationProviderClient?.requestLocationUpdates(request, getLocationUpdateIntent())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@ import javax.inject.Inject
|
|||
@SuppressLint("VisibleForTests") // https://issuetracker.google.com/issues/239451111
|
||||
class WearOnboardingListener : WearableListenerService() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WearOnboardingListener"
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var serverManager: ServerManager
|
||||
|
||||
|
@ -41,8 +45,16 @@ class WearOnboardingListener : WearableListenerService() {
|
|||
setUrgent()
|
||||
asPutDataRequest()
|
||||
}
|
||||
Wearable.getDataClient(this@WearOnboardingListener).putDataItem(putDataReq).addOnCompleteListener {
|
||||
Log.d("WearOnboardingListener", "sendHomeAssistantInstance: ${if (it.isSuccessful) "success" else "failed"}")
|
||||
try {
|
||||
Wearable.getDataClient(this@WearOnboardingListener).putDataItem(putDataReq)
|
||||
.addOnCompleteListener {
|
||||
Log.d(
|
||||
TAG,
|
||||
"sendHomeAssistantInstance: ${if (it.isSuccessful) "success" else "failed"}"
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to send home assistant instance", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -227,7 +227,11 @@ class ActivitySensorManager : BroadcastReceiver(), SensorManager {
|
|||
|
||||
Log.d(TAG, "Registering for activity updates.")
|
||||
val fastUpdate = SensorReceiverBase.shouldDoFastUpdates(context)
|
||||
actReg.requestActivityUpdates(TimeUnit.MINUTES.toMillis(if (fastUpdate) 1 else 2), pendingIntent)
|
||||
try {
|
||||
actReg.requestActivityUpdates(TimeUnit.MINUTES.toMillis(if (fastUpdate) 1 else 2), pendingIntent)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to register for activity updates", e)
|
||||
}
|
||||
}
|
||||
if ((
|
||||
isEnabled(context, sleepConfidence) || isEnabled(
|
||||
|
@ -238,47 +242,43 @@ class ActivitySensorManager : BroadcastReceiver(), SensorManager {
|
|||
) {
|
||||
val pendingIntent = getSleepPendingIntent(context)
|
||||
Log.d(TAG, "Registering for sleep updates")
|
||||
val task = when {
|
||||
(
|
||||
isEnabled(context, sleepConfidence) && !isEnabled(
|
||||
context,
|
||||
sleepSegment
|
||||
)
|
||||
) -> {
|
||||
Log.d(TAG, "Registering for sleep confidence updates only")
|
||||
ActivityRecognition.getClient(context).requestSleepSegmentUpdates(
|
||||
pendingIntent,
|
||||
SleepSegmentRequest(SleepSegmentRequest.CLASSIFY_EVENTS_ONLY)
|
||||
)
|
||||
try {
|
||||
val task = when {
|
||||
(isEnabled(context, sleepConfidence) && !isEnabled(context, sleepSegment)) -> {
|
||||
Log.d(TAG, "Registering for sleep confidence updates only")
|
||||
ActivityRecognition.getClient(context).requestSleepSegmentUpdates(
|
||||
pendingIntent,
|
||||
SleepSegmentRequest(SleepSegmentRequest.CLASSIFY_EVENTS_ONLY)
|
||||
)
|
||||
}
|
||||
|
||||
(!isEnabled(context, sleepConfidence) && isEnabled(context, sleepSegment)) -> {
|
||||
Log.d(TAG, "Registering for sleep segment updates only")
|
||||
ActivityRecognition.getClient(context).requestSleepSegmentUpdates(
|
||||
pendingIntent,
|
||||
SleepSegmentRequest(SleepSegmentRequest.SEGMENT_EVENTS_ONLY)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.d(TAG, "Registering for both sleep confidence and segment updates")
|
||||
ActivityRecognition.getClient(context).requestSleepSegmentUpdates(
|
||||
pendingIntent,
|
||||
SleepSegmentRequest.getDefaultSleepSegmentRequest()
|
||||
)
|
||||
}
|
||||
}
|
||||
(
|
||||
!isEnabled(context, sleepConfidence) && isEnabled(
|
||||
context,
|
||||
sleepSegment
|
||||
)
|
||||
) -> {
|
||||
Log.d(TAG, "Registering for sleep segment updates only")
|
||||
ActivityRecognition.getClient(context).requestSleepSegmentUpdates(
|
||||
pendingIntent,
|
||||
SleepSegmentRequest(SleepSegmentRequest.SEGMENT_EVENTS_ONLY)
|
||||
)
|
||||
task.addOnSuccessListener {
|
||||
Log.d(TAG, "Successfully registered for sleep updates")
|
||||
sleepRegistration = true
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Registering for both sleep confidence and segment updates")
|
||||
ActivityRecognition.getClient(context).requestSleepSegmentUpdates(
|
||||
pendingIntent,
|
||||
SleepSegmentRequest.getDefaultSleepSegmentRequest()
|
||||
)
|
||||
task.addOnFailureListener {
|
||||
Log.e(TAG, "Failed to register for sleep updates", it)
|
||||
ActivityRecognition.getClient(context).removeSleepSegmentUpdates(pendingIntent)
|
||||
sleepRegistration = false
|
||||
}
|
||||
}
|
||||
task.addOnSuccessListener {
|
||||
Log.d(TAG, "Successfully registered for sleep updates")
|
||||
sleepRegistration = true
|
||||
}
|
||||
task.addOnFailureListener {
|
||||
Log.e(TAG, "Failed to register for sleep updates", it)
|
||||
ActivityRecognition.getClient(context).removeSleepSegmentUpdates(pendingIntent)
|
||||
sleepRegistration = false
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to register for sleep updates", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.homeassistant.companion.android.sensors
|
|||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.car.app.connection.CarConnection
|
||||
import androidx.lifecycle.Observer
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
|
@ -56,7 +57,12 @@ class AndroidAutoSensorManager : SensorManager, Observer<Int> {
|
|||
}
|
||||
CoroutineScope(Dispatchers.Main + Job()).launch {
|
||||
if (carConnection == null) {
|
||||
carConnection = CarConnection(context.applicationContext)
|
||||
carConnection = try {
|
||||
CarConnection(context.applicationContext)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get car connection", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
carConnection?.type?.observeForever(this@AndroidAutoSensorManager)
|
||||
}
|
||||
|
|
|
@ -75,7 +75,12 @@ class GeocodeSensorManager : SensorManager {
|
|||
return
|
||||
}
|
||||
|
||||
val location = LocationServices.getFusedLocationProviderClient(context).lastLocation.await()
|
||||
val location = try {
|
||||
LocationServices.getFusedLocationProviderClient(context).lastLocation.await()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get fused location provider client", e)
|
||||
null
|
||||
}
|
||||
var address: Address? = null
|
||||
try {
|
||||
if (location == null) {
|
||||
|
@ -128,7 +133,13 @@ class GeocodeSensorManager : SensorManager {
|
|||
|
||||
val prettyAddress = address?.getAddressLine(0)
|
||||
|
||||
HighAccuracyLocationService.updateNotificationAddress(context, location, if (!prettyAddress.isNullOrEmpty()) prettyAddress else context.getString(commonR.string.unknown_address))
|
||||
if (location != null) {
|
||||
HighAccuracyLocationService.updateNotificationAddress(
|
||||
context,
|
||||
location,
|
||||
if (!prettyAddress.isNullOrEmpty()) prettyAddress else context.getString(commonR.string.unknown_address)
|
||||
)
|
||||
}
|
||||
|
||||
onSensorUpdated(
|
||||
context,
|
||||
|
|
|
@ -647,7 +647,13 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
|||
}
|
||||
Log.d(TAG, "Registering for location updates.")
|
||||
|
||||
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(latestContext)
|
||||
fusedLocationProviderClient = try {
|
||||
LocationServices.getFusedLocationProviderClient(latestContext)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to get fused location provider client", e)
|
||||
null
|
||||
}
|
||||
|
||||
val intent = getLocationUpdateIntent(false)
|
||||
|
||||
fusedLocationProviderClient?.requestLocationUpdates(
|
||||
|
@ -1109,65 +1115,88 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
|||
setMaxUpdates(maxRetries)
|
||||
setMinUpdateIntervalMillis(5000)
|
||||
}.build()
|
||||
LocationServices.getFusedLocationProviderClient(latestContext)
|
||||
.requestLocationUpdates(
|
||||
request,
|
||||
object : LocationCallback() {
|
||||
val wakeLock: PowerManager.WakeLock? =
|
||||
latestContext.getSystemService<PowerManager>()
|
||||
?.newWakeLock(
|
||||
PowerManager.PARTIAL_WAKE_LOCK,
|
||||
"HomeAssistant::AccurateLocation"
|
||||
)?.apply { acquire(10 * 60 * 1000L /*10 minutes*/) }
|
||||
var numberCalls = 0
|
||||
override fun onLocationResult(locationResult: LocationResult) {
|
||||
numberCalls++
|
||||
Log.d(
|
||||
TAG,
|
||||
"Got single accurate location update: ${locationResult.lastLocation}"
|
||||
)
|
||||
if (locationResult.equals(null)) {
|
||||
Log.w(TAG, "No location provided.")
|
||||
return
|
||||
}
|
||||
try {
|
||||
LocationServices.getFusedLocationProviderClient(latestContext)
|
||||
.requestLocationUpdates(
|
||||
request,
|
||||
object : LocationCallback() {
|
||||
val wakeLock: PowerManager.WakeLock? =
|
||||
latestContext.getSystemService<PowerManager>()
|
||||
?.newWakeLock(
|
||||
PowerManager.PARTIAL_WAKE_LOCK,
|
||||
"HomeAssistant::AccurateLocation"
|
||||
)?.apply { acquire(10 * 60 * 1000L /*10 minutes*/) }
|
||||
var numberCalls = 0
|
||||
override fun onLocationResult(locationResult: LocationResult) {
|
||||
numberCalls++
|
||||
Log.d(
|
||||
TAG,
|
||||
"Got single accurate location update: ${locationResult.lastLocation}"
|
||||
)
|
||||
if (locationResult.equals(null)) {
|
||||
Log.w(TAG, "No location provided.")
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
locationResult.lastLocation!!.accuracy <= minAccuracy -> {
|
||||
Log.d(TAG, "Location accurate enough, all done with high accuracy.")
|
||||
runBlocking {
|
||||
locationResult.lastLocation?.let {
|
||||
getEnabledServers(latestContext, singleAccurateLocation).forEach { serverId ->
|
||||
sendLocationUpdate(it, serverId, LocationUpdateTrigger.SINGLE_ACCURATE_LOCATION)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wakeLock?.isHeld == true) wakeLock.release()
|
||||
}
|
||||
numberCalls >= maxRetries -> {
|
||||
Log.d(
|
||||
TAG,
|
||||
"No location was accurate enough, sending our last location anyway"
|
||||
)
|
||||
if (locationResult.lastLocation!!.accuracy <= minAccuracy * 2) {
|
||||
when {
|
||||
locationResult.lastLocation!!.accuracy <= minAccuracy -> {
|
||||
Log.d(
|
||||
TAG,
|
||||
"Location accurate enough, all done with high accuracy."
|
||||
)
|
||||
runBlocking {
|
||||
getEnabledServers(latestContext, singleAccurateLocation).forEach { serverId ->
|
||||
sendLocationUpdate(locationResult.lastLocation!!, serverId, LocationUpdateTrigger.SINGLE_ACCURATE_LOCATION)
|
||||
locationResult.lastLocation?.let {
|
||||
getEnabledServers(
|
||||
latestContext,
|
||||
singleAccurateLocation
|
||||
).forEach { serverId ->
|
||||
sendLocationUpdate(
|
||||
it,
|
||||
serverId,
|
||||
LocationUpdateTrigger.SINGLE_ACCURATE_LOCATION
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wakeLock?.isHeld == true) wakeLock.release()
|
||||
}
|
||||
|
||||
numberCalls >= maxRetries -> {
|
||||
Log.d(
|
||||
TAG,
|
||||
"No location was accurate enough, sending our last location anyway"
|
||||
)
|
||||
if (locationResult.lastLocation!!.accuracy <= minAccuracy * 2) {
|
||||
runBlocking {
|
||||
getEnabledServers(
|
||||
latestContext,
|
||||
singleAccurateLocation
|
||||
).forEach { serverId ->
|
||||
sendLocationUpdate(
|
||||
locationResult.lastLocation!!,
|
||||
serverId,
|
||||
LocationUpdateTrigger.SINGLE_ACCURATE_LOCATION
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wakeLock?.isHeld == true) wakeLock.release()
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(
|
||||
TAG,
|
||||
"Location not accurate enough on retry $numberCalls of $maxRetries"
|
||||
)
|
||||
}
|
||||
if (wakeLock?.isHeld == true) wakeLock.release()
|
||||
}
|
||||
else -> {
|
||||
Log.w(
|
||||
TAG,
|
||||
"Location not accurate enough on retry $numberCalls of $maxRetries"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Looper.getMainLooper()
|
||||
)
|
||||
},
|
||||
Looper.getMainLooper()
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get location data for single accurate sensor", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun docsLink(): String {
|
||||
|
|
|
@ -34,9 +34,9 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC
|
|||
|
||||
private lateinit var binding: ActivitySettingsWearBinding
|
||||
|
||||
private lateinit var capabilityClient: CapabilityClient
|
||||
private lateinit var nodeClient: NodeClient
|
||||
private lateinit var remoteActivityHelper: RemoteActivityHelper
|
||||
private var capabilityClient: CapabilityClient? = null
|
||||
private var nodeClient: NodeClient? = null
|
||||
private var remoteActivityHelper: RemoteActivityHelper? = null
|
||||
|
||||
private var wearNodesWithApp: Set<Node>? = null
|
||||
private var allConnectedNodes: List<Node>? = null
|
||||
|
@ -49,9 +49,24 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC
|
|||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
capabilityClient = Wearable.getCapabilityClient(this)
|
||||
nodeClient = Wearable.getNodeClient(this)
|
||||
remoteActivityHelper = RemoteActivityHelper(this)
|
||||
capabilityClient = try {
|
||||
Wearable.getCapabilityClient(this)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to get capability client", e)
|
||||
null
|
||||
}
|
||||
nodeClient = try {
|
||||
Wearable.getNodeClient(this)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to get node client", e)
|
||||
null
|
||||
}
|
||||
remoteActivityHelper = try {
|
||||
RemoteActivityHelper(this)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to get remote activity helper", e)
|
||||
null
|
||||
}
|
||||
|
||||
binding.remoteOpenButton.setOnClickListener {
|
||||
openPlayStoreOnWearDevicesWithoutApp()
|
||||
|
@ -81,13 +96,13 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC
|
|||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
capabilityClient.removeListener(this, CAPABILITY_WEAR_APP)
|
||||
capabilityClient?.removeListener(this, CAPABILITY_WEAR_APP)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
title = getString(commonR.string.wear_os_settings_title)
|
||||
capabilityClient.addListener(this, CAPABILITY_WEAR_APP)
|
||||
capabilityClient?.addListener(this, CAPABILITY_WEAR_APP)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -106,11 +121,11 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC
|
|||
private suspend fun findWearDevicesWithApp() {
|
||||
try {
|
||||
val capabilityInfo = capabilityClient
|
||||
.getCapability(CAPABILITY_WEAR_APP, CapabilityClient.FILTER_ALL)
|
||||
.await()
|
||||
?.getCapability(CAPABILITY_WEAR_APP, CapabilityClient.FILTER_ALL)
|
||||
?.await()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
wearNodesWithApp = capabilityInfo.nodes
|
||||
wearNodesWithApp = capabilityInfo?.nodes
|
||||
Log.d(TAG, "Capable Nodes: $wearNodesWithApp")
|
||||
updateUI()
|
||||
}
|
||||
|
@ -124,7 +139,7 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC
|
|||
|
||||
private suspend fun findAllWearDevices() {
|
||||
try {
|
||||
val connectedNodes = nodeClient.connectedNodes.await()
|
||||
val connectedNodes = nodeClient?.connectedNodes?.await()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
allConnectedNodes = connectedNodes
|
||||
|
@ -187,11 +202,11 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC
|
|||
lifecycleScope.launch {
|
||||
try {
|
||||
remoteActivityHelper
|
||||
.startRemoteActivity(
|
||||
?.startRemoteActivity(
|
||||
targetIntent = intent,
|
||||
targetNodeId = node.id
|
||||
)
|
||||
.await()
|
||||
?.await()
|
||||
|
||||
Toast.makeText(
|
||||
this@SettingsWearActivity,
|
||||
|
|
|
@ -84,7 +84,11 @@ class SettingsWearViewModel @Inject constructor(
|
|||
val resultSnackbar = _resultSnackbar.asSharedFlow()
|
||||
|
||||
init {
|
||||
Wearable.getDataClient(application).addListener(this)
|
||||
try {
|
||||
Wearable.getDataClient(application).addListener(this)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to get wearable data client", e)
|
||||
}
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val capabilityInfo = Wearable.getCapabilityClient(application)
|
||||
|
@ -113,7 +117,11 @@ class SettingsWearViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onCleared() {
|
||||
Wearable.getDataClient(getApplication<HomeAssistantApplication>()).removeListener(this)
|
||||
try {
|
||||
Wearable.getDataClient(getApplication<HomeAssistantApplication>()).removeListener(this)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to remove listener from wearable data client", e)
|
||||
}
|
||||
|
||||
if (serverId != 0) {
|
||||
CoroutineScope(Dispatchers.Main + Job()).launch {
|
||||
|
@ -218,15 +226,18 @@ class SettingsWearViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
val app = getApplication<HomeAssistantApplication>()
|
||||
Wearable.getDataClient(app).putDataItem(putDataRequest).apply {
|
||||
addOnSuccessListener { Log.d(TAG, "Successfully sent auth to wear") }
|
||||
addOnFailureListener { e ->
|
||||
Log.e(TAG, "Failed to send auth to wear", e)
|
||||
_hasData.value = true
|
||||
viewModelScope.launch {
|
||||
_resultSnackbar.emit(app.getString(commonR.string.failed_watch_connection))
|
||||
try {
|
||||
Wearable.getDataClient(app).putDataItem(putDataRequest).apply {
|
||||
addOnSuccessListener { Log.d(TAG, "Successfully sent auth to wear") }
|
||||
addOnFailureListener { e ->
|
||||
Log.e(TAG, "Failed to send auth to wear", e)
|
||||
_hasData.value = true
|
||||
watchConnectionError(app)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to send auth to wear", e)
|
||||
watchConnectionError(app)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,9 +249,14 @@ class SettingsWearViewModel @Inject constructor(
|
|||
asPutDataRequest()
|
||||
}
|
||||
|
||||
Wearable.getDataClient(getApplication<HomeAssistantApplication>()).putDataItem(putDataRequest).apply {
|
||||
addOnSuccessListener { Log.d(TAG, "Successfully sent tile template to wear") }
|
||||
addOnFailureListener { e -> Log.e(TAG, "Failed to send tile template to wear", e) }
|
||||
try {
|
||||
Wearable.getDataClient(getApplication<HomeAssistantApplication>())
|
||||
.putDataItem(putDataRequest).apply {
|
||||
addOnSuccessListener { Log.d(TAG, "Successfully sent tile template to wear") }
|
||||
addOnFailureListener { e -> Log.e(TAG, "Failed to send tile template to wear", e) }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to send template tile to wear", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -348,4 +364,10 @@ class SettingsWearViewModel @Inject constructor(
|
|||
|
||||
authenticateId = null
|
||||
}
|
||||
|
||||
private fun watchConnectionError(app: HomeAssistantApplication) {
|
||||
viewModelScope.launch {
|
||||
_resultSnackbar.emit(app.getString(commonR.string.failed_watch_connection))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user