Merge "Prevent recursive calls to proximity sensor." into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
92be13d838
@@ -34,6 +34,7 @@ import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.statusbar.phone.BiometricUnlockController;
|
||||
import com.android.systemui.statusbar.phone.DozeParameters;
|
||||
import com.android.systemui.statusbar.policy.BatteryController;
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||
import com.android.systemui.util.sensors.AsyncSensorManager;
|
||||
import com.android.systemui.util.sensors.ProximitySensor;
|
||||
import com.android.systemui.util.wakelock.DelayedWakeLock;
|
||||
@@ -56,6 +57,7 @@ public class DozeFactory {
|
||||
private final ProximitySensor mProximitySensor;
|
||||
private final DelayedWakeLock.Builder mDelayedWakeLockBuilder;
|
||||
private final Handler mHandler;
|
||||
private final DelayableExecutor mDelayableExecutor;
|
||||
private final BiometricUnlockController mBiometricUnlockController;
|
||||
private final BroadcastDispatcher mBroadcastDispatcher;
|
||||
private final DozeHost mDozeHost;
|
||||
@@ -68,6 +70,7 @@ public class DozeFactory {
|
||||
DockManager dockManager, @Nullable IWallpaperManager wallpaperManager,
|
||||
ProximitySensor proximitySensor,
|
||||
DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
|
||||
DelayableExecutor delayableExecutor,
|
||||
BiometricUnlockController biometricUnlockController,
|
||||
BroadcastDispatcher broadcastDispatcher, DozeHost dozeHost) {
|
||||
mFalsingManager = falsingManager;
|
||||
@@ -83,6 +86,7 @@ public class DozeFactory {
|
||||
mProximitySensor = proximitySensor;
|
||||
mDelayedWakeLockBuilder = delayedWakeLockBuilder;
|
||||
mHandler = handler;
|
||||
mDelayableExecutor = delayableExecutor;
|
||||
mBiometricUnlockController = biometricUnlockController;
|
||||
mBroadcastDispatcher = broadcastDispatcher;
|
||||
mDozeHost = dozeHost;
|
||||
@@ -107,8 +111,8 @@ public class DozeFactory {
|
||||
new DozePauser(mHandler, machine, mAlarmManager, mDozeParameters.getPolicy()),
|
||||
new DozeFalsingManagerAdapter(mFalsingManager),
|
||||
createDozeTriggers(dozeService, mAsyncSensorManager, mDozeHost,
|
||||
mAlarmManager, config, mDozeParameters, mHandler, wakeLock, machine,
|
||||
mDockManager, mDozeLog),
|
||||
mAlarmManager, config, mDozeParameters, mDelayableExecutor, wakeLock,
|
||||
machine, mDockManager, mDozeLog),
|
||||
createDozeUi(dozeService, mDozeHost, wakeLock, machine, mHandler,
|
||||
mAlarmManager, mDozeParameters, mDozeLog),
|
||||
new DozeScreenState(wrappedService, mHandler, mDozeHost, mDozeParameters,
|
||||
@@ -135,11 +139,11 @@ public class DozeFactory {
|
||||
|
||||
private DozeTriggers createDozeTriggers(Context context, AsyncSensorManager sensorManager,
|
||||
DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config,
|
||||
DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine,
|
||||
DockManager dockManager, DozeLog dozeLog) {
|
||||
DozeParameters params, DelayableExecutor delayableExecutor, WakeLock wakeLock,
|
||||
DozeMachine machine, DockManager dockManager, DozeLog dozeLog) {
|
||||
boolean allowPulseTriggers = true;
|
||||
return new DozeTriggers(context, machine, host, alarmManager, config, params,
|
||||
sensorManager, handler, wakeLock, allowPulseTriggers, dockManager,
|
||||
sensorManager, delayableExecutor, wakeLock, allowPulseTriggers, dockManager,
|
||||
mProximitySensor, dozeLog, mBroadcastDispatcher);
|
||||
|
||||
}
|
||||
|
||||
@@ -101,7 +101,8 @@ public class DozeSensors {
|
||||
|
||||
public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
|
||||
DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
|
||||
Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog) {
|
||||
Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog,
|
||||
ProximitySensor proximitySensor) {
|
||||
mContext = context;
|
||||
mAlarmManager = alarmManager;
|
||||
mSensorManager = sensorManager;
|
||||
@@ -111,6 +112,7 @@ public class DozeSensors {
|
||||
mProxCallback = proxCallback;
|
||||
mResolver = mContext.getContentResolver();
|
||||
mCallback = callback;
|
||||
mProximitySensor = proximitySensor;
|
||||
|
||||
boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
|
||||
mSensors = new TriggerSensor[] {
|
||||
@@ -173,7 +175,6 @@ public class DozeSensors {
|
||||
dozeLog),
|
||||
};
|
||||
|
||||
mProximitySensor = new ProximitySensor(context.getResources(), sensorManager);
|
||||
setProxListening(false); // Don't immediately start listening when we register.
|
||||
mProximitySensor.register(
|
||||
proximityEvent -> {
|
||||
|
||||
@@ -26,7 +26,6 @@ import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.display.AmbientDisplayConfiguration;
|
||||
import android.metrics.LogMaker;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.text.format.Formatter;
|
||||
@@ -43,6 +42,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.dock.DockManager;
|
||||
import com.android.systemui.statusbar.phone.DozeParameters;
|
||||
import com.android.systemui.util.Assert;
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||
import com.android.systemui.util.sensors.AsyncSensorManager;
|
||||
import com.android.systemui.util.sensors.ProximitySensor;
|
||||
import com.android.systemui.util.wakelock.WakeLock;
|
||||
@@ -152,9 +152,9 @@ public class DozeTriggers implements DozeMachine.Part {
|
||||
|
||||
public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
|
||||
AlarmManager alarmManager, AmbientDisplayConfiguration config,
|
||||
DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler,
|
||||
WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager,
|
||||
ProximitySensor proximitySensor,
|
||||
DozeParameters dozeParameters, AsyncSensorManager sensorManager,
|
||||
DelayableExecutor delayableExecutor, WakeLock wakeLock, boolean allowPulseTriggers,
|
||||
DockManager dockManager, ProximitySensor proximitySensor,
|
||||
DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher) {
|
||||
mContext = context;
|
||||
mMachine = machine;
|
||||
@@ -165,10 +165,10 @@ public class DozeTriggers implements DozeMachine.Part {
|
||||
mWakeLock = wakeLock;
|
||||
mAllowPulseTriggers = allowPulseTriggers;
|
||||
mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
|
||||
config, wakeLock, this::onSensor, this::onProximityFar, dozeLog);
|
||||
config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor);
|
||||
mUiModeManager = mContext.getSystemService(UiModeManager.class);
|
||||
mDockManager = dockManager;
|
||||
mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler);
|
||||
mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, delayableExecutor);
|
||||
mDozeLog = dozeLog;
|
||||
mBroadcastDispatcher = broadcastDispatcher;
|
||||
}
|
||||
|
||||
@@ -21,16 +21,17 @@ import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -49,8 +50,9 @@ public class ProximitySensor {
|
||||
private String mTag = null;
|
||||
@VisibleForTesting ProximityEvent mLastEvent;
|
||||
private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;
|
||||
private boolean mPaused;
|
||||
@VisibleForTesting protected boolean mPaused;
|
||||
private boolean mRegistered;
|
||||
private final AtomicBoolean mAlerting = new AtomicBoolean();
|
||||
|
||||
private SensorEventListener mSensorEventListener = new SensorEventListener() {
|
||||
@Override
|
||||
@@ -217,8 +219,12 @@ public class ProximitySensor {
|
||||
|
||||
/** Update all listeners with the last value this class received from the sensor. */
|
||||
public void alertListeners() {
|
||||
if (mAlerting.getAndSet(true)) {
|
||||
return;
|
||||
}
|
||||
mListeners.forEach(proximitySensorListener ->
|
||||
proximitySensorListener.onSensorEvent(mLastEvent));
|
||||
mAlerting.set(false);
|
||||
}
|
||||
|
||||
private void onSensorEvent(SensorEvent event) {
|
||||
@@ -239,14 +245,14 @@ public class ProximitySensor {
|
||||
public static class ProximityCheck implements Runnable {
|
||||
|
||||
private final ProximitySensor mSensor;
|
||||
private final Handler mHandler;
|
||||
private final DelayableExecutor mDelayableExecutor;
|
||||
private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
|
||||
|
||||
@Inject
|
||||
public ProximityCheck(ProximitySensor sensor, Handler handler) {
|
||||
public ProximityCheck(ProximitySensor sensor, DelayableExecutor delayableExecutor) {
|
||||
mSensor = sensor;
|
||||
mSensor.setTag("prox_check");
|
||||
mHandler = handler;
|
||||
mDelayableExecutor = delayableExecutor;
|
||||
mSensor.pause();
|
||||
ProximitySensorListener listener = proximityEvent -> {
|
||||
mCallbacks.forEach(
|
||||
@@ -280,7 +286,7 @@ public class ProximitySensor {
|
||||
mCallbacks.add(callback);
|
||||
if (!mSensor.isRegistered()) {
|
||||
mSensor.resume();
|
||||
mHandler.postDelayed(this, timeoutMs);
|
||||
mDelayableExecutor.executeDelayed(this, timeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.systemui.doze;
|
||||
|
||||
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyFloat;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
@@ -46,6 +47,7 @@ import com.android.systemui.doze.DozeSensors.TriggerSensor;
|
||||
import com.android.systemui.plugins.SensorManagerPlugin;
|
||||
import com.android.systemui.statusbar.phone.DozeParameters;
|
||||
import com.android.systemui.util.sensors.AsyncSensorManager;
|
||||
import com.android.systemui.util.sensors.ProximitySensor;
|
||||
import com.android.systemui.util.wakelock.WakeLock;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -82,7 +84,7 @@ public class DozeSensorsTest extends SysuiTestCase {
|
||||
@Mock
|
||||
private DozeLog mDozeLog;
|
||||
@Mock
|
||||
private Sensor mProximitySensor;
|
||||
private ProximitySensor mProximitySensor;
|
||||
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
|
||||
private TestableLooper mTestableLooper;
|
||||
private DozeSensors mDozeSensors;
|
||||
@@ -93,7 +95,6 @@ public class DozeSensorsTest extends SysuiTestCase {
|
||||
mTestableLooper = TestableLooper.get(this);
|
||||
when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
|
||||
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
|
||||
when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProximitySensor);
|
||||
doAnswer(invocation -> {
|
||||
((Runnable) invocation.getArgument(0)).run();
|
||||
return null;
|
||||
@@ -103,10 +104,9 @@ public class DozeSensorsTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void testRegisterProx() {
|
||||
// We should not register with the sensor manager initially.
|
||||
verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
|
||||
assertFalse(mProximitySensor.isRegistered());
|
||||
mDozeSensors.setProxListening(true);
|
||||
verify(mSensorManager).registerListener(any(), any(Sensor.class), anyInt());
|
||||
verify(mProximitySensor).resume();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -169,7 +169,8 @@ public class DozeSensorsTest extends SysuiTestCase {
|
||||
|
||||
TestableDozeSensors() {
|
||||
super(getContext(), mAlarmManager, mSensorManager, mDozeParameters,
|
||||
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog);
|
||||
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
|
||||
mProximitySensor);
|
||||
for (TriggerSensor sensor : mSensors) {
|
||||
if (sensor instanceof PluginSensor
|
||||
&& ((PluginSensor) sensor).mPluginSensor.getType()
|
||||
|
||||
@@ -31,7 +31,6 @@ import android.app.AlarmManager;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.display.AmbientDisplayConfiguration;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.testing.TestableLooper.RunWithLooper;
|
||||
@@ -42,10 +41,12 @@ import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.dock.DockManager;
|
||||
import com.android.systemui.statusbar.phone.DozeParameters;
|
||||
import com.android.systemui.util.concurrency.FakeExecutor;
|
||||
import com.android.systemui.util.sensors.AsyncSensorManager;
|
||||
import com.android.systemui.util.sensors.FakeProximitySensor;
|
||||
import com.android.systemui.util.sensors.FakeSensorManager;
|
||||
import com.android.systemui.util.sensors.ProximitySensor;
|
||||
import com.android.systemui.util.time.FakeSystemClock;
|
||||
import com.android.systemui.util.wakelock.WakeLock;
|
||||
import com.android.systemui.util.wakelock.WakeLockFake;
|
||||
|
||||
@@ -75,6 +76,7 @@ public class DozeTriggersTest extends SysuiTestCase {
|
||||
private FakeSensorManager mSensors;
|
||||
private Sensor mTapSensor;
|
||||
private FakeProximitySensor mProximitySensor;
|
||||
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -89,7 +91,7 @@ public class DozeTriggersTest extends SysuiTestCase {
|
||||
mProximitySensor = new FakeProximitySensor(getContext().getResources(), asyncSensorManager);
|
||||
|
||||
mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, config, parameters,
|
||||
asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true,
|
||||
asyncSensorManager, mFakeExecutor, wakeLock, true,
|
||||
mDockManager, mProximitySensor, mock(DozeLog.class), mBroadcastDispatcher);
|
||||
waitForSensorManager();
|
||||
}
|
||||
@@ -111,9 +113,8 @@ public class DozeTriggersTest extends SysuiTestCase {
|
||||
verify(mMachine, never()).requestState(any());
|
||||
verify(mMachine, never()).requestPulse(anyInt());
|
||||
|
||||
captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
|
||||
waitForSensorManager();
|
||||
mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(false, 2));
|
||||
captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
|
||||
mProximitySensor.alertListeners();
|
||||
|
||||
verify(mMachine).requestPulse(anyInt());
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.content.res.Resources;
|
||||
|
||||
public class FakeProximitySensor extends ProximitySensor {
|
||||
private boolean mAvailable;
|
||||
private boolean mRegistered;
|
||||
|
||||
public FakeProximitySensor(Resources resources, AsyncSensorManager sensorManager) {
|
||||
super(resources, sensorManager);
|
||||
@@ -34,6 +35,11 @@ public class FakeProximitySensor extends ProximitySensor {
|
||||
mLastEvent = event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered() {
|
||||
return mRegistered;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getSensorAvailable() {
|
||||
return mAvailable;
|
||||
@@ -41,11 +47,11 @@ public class FakeProximitySensor extends ProximitySensor {
|
||||
|
||||
@Override
|
||||
protected void registerInternal() {
|
||||
// no-op
|
||||
mRegistered = !mPaused;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterInternal() {
|
||||
// no-op
|
||||
mRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.util.sensors;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.util.concurrency.FakeExecutor;
|
||||
import com.android.systemui.util.time.FakeSystemClock;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class ProximityCheckTest extends SysuiTestCase {
|
||||
|
||||
private FakeProximitySensor mFakeProximitySensor;
|
||||
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
|
||||
private TestableCallback mTestableCallback = new TestableCallback();
|
||||
|
||||
private ProximitySensor.ProximityCheck mProximityCheck;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
AsyncSensorManager asyncSensorManager =
|
||||
new AsyncSensorManager(new FakeSensorManager(mContext), null, new Handler());
|
||||
mFakeProximitySensor = new FakeProximitySensor(mContext.getResources(), asyncSensorManager);
|
||||
|
||||
mProximityCheck = new ProximitySensor.ProximityCheck(mFakeProximitySensor, mFakeExecutor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheck() {
|
||||
mProximityCheck.check(100, mTestableCallback);
|
||||
|
||||
assertNull(mTestableCallback.mLastResult);
|
||||
|
||||
mFakeProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(true, 0));
|
||||
mFakeProximitySensor.alertListeners();
|
||||
|
||||
assertTrue(mTestableCallback.mLastResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTimeout() {
|
||||
mProximityCheck.check(100, mTestableCallback);
|
||||
|
||||
assertTrue(mFakeProximitySensor.isRegistered());
|
||||
|
||||
mFakeExecutor.advanceClockToNext();
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
assertFalse(mFakeProximitySensor.isRegistered());
|
||||
}
|
||||
|
||||
private static class TestableCallback implements Consumer<Boolean> {
|
||||
Boolean mLastResult;
|
||||
@Override
|
||||
public void accept(Boolean result) {
|
||||
mLastResult = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,6 +219,26 @@ public class ProximitySensorTest extends SysuiTestCase {
|
||||
waitForSensorManager();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreventRecursiveAlert() {
|
||||
TestableListener listenerA = new TestableListener() {
|
||||
@Override
|
||||
public void onSensorEvent(ProximitySensor.ProximityEvent proximityEvent) {
|
||||
super.onSensorEvent(proximityEvent);
|
||||
if (mCallCount < 2) {
|
||||
mProximitySensor.alertListeners();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mProximitySensor.register(listenerA);
|
||||
|
||||
mProximitySensor.alertListeners();
|
||||
|
||||
assertEquals(1, listenerA.mCallCount);
|
||||
}
|
||||
|
||||
|
||||
class TestableListener implements ProximitySensor.ProximitySensorListener {
|
||||
ProximitySensor.ProximityEvent mLastEvent;
|
||||
int mCallCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user