Merge "Fade out UFPS icon when launching an app over it (1/2)"
This commit is contained in:
committed by
Android (Google) Code Review
commit
c00e49dd29
@@ -39,5 +39,5 @@ android_library {
|
||||
],
|
||||
|
||||
manifest: "AndroidManifest.xml",
|
||||
|
||||
kotlincflags: ["-Xjvm-default=enable"],
|
||||
}
|
||||
|
||||
@@ -95,6 +95,9 @@ class ActivityLaunchAnimator(
|
||||
*/
|
||||
var callback: Callback? = null
|
||||
|
||||
/** The set of [Listener] that should be notified of any animation started by this animator. */
|
||||
private val listeners = LinkedHashSet<Listener>()
|
||||
|
||||
/**
|
||||
* Start an intent and animate the opening window. The intent will be started by running
|
||||
* [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
|
||||
@@ -214,6 +217,16 @@ class ActivityLaunchAnimator(
|
||||
}
|
||||
}
|
||||
|
||||
/** Add a [Listener] that can listen to launch animations. */
|
||||
fun addListener(listener: Listener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
/** Remove a [Listener]. */
|
||||
fun removeListener(listener: Listener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
/** Create a new animation [Runner] controlled by [controller]. */
|
||||
@VisibleForTesting
|
||||
fun createRunner(controller: Controller): Runner = Runner(controller)
|
||||
@@ -234,13 +247,27 @@ class ActivityLaunchAnimator(
|
||||
/** Hide the keyguard and animate using [runner]. */
|
||||
fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner)
|
||||
|
||||
/** Enable/disable window blur so they don't overlap with the window launch animation **/
|
||||
fun setBlursDisabledForAppLaunch(disabled: Boolean)
|
||||
|
||||
/* Get the background color of [task]. */
|
||||
fun getBackgroundColor(task: TaskInfo): Int
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
/** Called when an activity launch animation started. */
|
||||
@JvmDefault
|
||||
fun onLaunchAnimationStart() {}
|
||||
|
||||
/**
|
||||
* Called when an activity launch animation is finished. This will be called if and only if
|
||||
* [onLaunchAnimationStart] was called earlier.
|
||||
*/
|
||||
@JvmDefault
|
||||
fun onLaunchAnimationEnd() {}
|
||||
|
||||
/** Called when an activity launch animation made progress. */
|
||||
@JvmDefault
|
||||
fun onLaunchAnimationProgress(linearProgress: Float) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A controller that takes care of applying the animation to an expanding view.
|
||||
*
|
||||
@@ -396,12 +423,12 @@ class ActivityLaunchAnimator(
|
||||
val delegate = this.controller
|
||||
val controller = object : LaunchAnimator.Controller by delegate {
|
||||
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
|
||||
callback.setBlursDisabledForAppLaunch(true)
|
||||
listeners.forEach { it.onLaunchAnimationStart() }
|
||||
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
|
||||
}
|
||||
|
||||
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
|
||||
callback.setBlursDisabledForAppLaunch(false)
|
||||
listeners.forEach { it.onLaunchAnimationEnd() }
|
||||
iCallback?.invoke()
|
||||
delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
|
||||
}
|
||||
@@ -413,6 +440,7 @@ class ActivityLaunchAnimator(
|
||||
) {
|
||||
applyStateToWindow(window, state)
|
||||
navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
|
||||
listeners.forEach { it.onLaunchAnimationProgress(linearProgress) }
|
||||
delegate.onLaunchAnimationProgress(state, progress, linearProgress)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ import android.view.accessibility.AccessibilityManager;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.LatencyTracker;
|
||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||
import com.android.systemui.animation.ActivityLaunchAnimator;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.doze.DozeReceiver;
|
||||
@@ -130,6 +131,7 @@ public class UdfpsController implements DozeReceiver {
|
||||
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
|
||||
// sensors, this, in addition to a lot of the code here, will be updated.
|
||||
@VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
|
||||
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
|
||||
|
||||
// Tracks the velocity of a touch to help filter out the touches that move too fast.
|
||||
@Nullable private VelocityTracker mVelocityTracker;
|
||||
@@ -198,7 +200,8 @@ public class UdfpsController implements DozeReceiver {
|
||||
mLockscreenShadeTransitionController, mConfigurationController,
|
||||
mSystemClock, mKeyguardStateController,
|
||||
mUnlockedScreenOffAnimationController, mSensorProps, mHbmProvider,
|
||||
reason, callback, UdfpsController.this::onTouch)));
|
||||
reason, callback, UdfpsController.this::onTouch,
|
||||
mActivityLaunchAnimator)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -487,7 +490,8 @@ public class UdfpsController implements DozeReceiver {
|
||||
@NonNull SystemClock systemClock,
|
||||
@NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
|
||||
@NonNull SystemUIDialogManager dialogManager,
|
||||
@NonNull LatencyTracker latencyTracker) {
|
||||
@NonNull LatencyTracker latencyTracker,
|
||||
@NonNull ActivityLaunchAnimator activityLaunchAnimator) {
|
||||
mContext = context;
|
||||
mExecution = execution;
|
||||
mVibrator = vibrator;
|
||||
@@ -516,6 +520,7 @@ public class UdfpsController implements DozeReceiver {
|
||||
mSystemClock = systemClock;
|
||||
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
|
||||
mLatencyTracker = latencyTracker;
|
||||
mActivityLaunchAnimator = activityLaunchAnimator;
|
||||
|
||||
mSensorProps = findFirstUdfps();
|
||||
// At least one UDFPS sensor exists
|
||||
|
||||
@@ -41,6 +41,7 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan
|
||||
import androidx.annotation.LayoutRes
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||
import com.android.systemui.statusbar.LockscreenShadeTransitionController
|
||||
@@ -81,7 +82,8 @@ class UdfpsControllerOverlay(
|
||||
private var hbmProvider: UdfpsHbmProvider,
|
||||
@ShowReason val requestReason: Int,
|
||||
private val controllerCallback: IUdfpsOverlayControllerCallback,
|
||||
private val onTouch: (View, MotionEvent, Boolean) -> Boolean
|
||||
private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
|
||||
private val activityLaunchAnimator: ActivityLaunchAnimator
|
||||
) {
|
||||
/** The view, when [isShowing], or null. */
|
||||
var overlayView: UdfpsView? = null
|
||||
@@ -200,7 +202,8 @@ class UdfpsControllerOverlay(
|
||||
keyguardStateController,
|
||||
unlockedScreenOffAnimationController,
|
||||
dialogManager,
|
||||
controller
|
||||
controller,
|
||||
activityLaunchAnimator
|
||||
)
|
||||
}
|
||||
BiometricOverlayConstants.REASON_AUTH_BP -> {
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.view.MotionEvent;
|
||||
|
||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.animation.ActivityLaunchAnimator;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
|
||||
@@ -55,6 +56,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
|
||||
@NonNull private final UdfpsController mUdfpsController;
|
||||
@NonNull private final UnlockedScreenOffAnimationController
|
||||
mUnlockedScreenOffAnimationController;
|
||||
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
|
||||
|
||||
private boolean mShowingUdfpsBouncer;
|
||||
private boolean mUdfpsRequested;
|
||||
@@ -66,6 +68,8 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
|
||||
private long mLastUdfpsBouncerShowTime = -1;
|
||||
private float mStatusBarExpansion;
|
||||
private boolean mLaunchTransitionFadingAway;
|
||||
private boolean mIsLaunchingActivity;
|
||||
private float mActivityLaunchProgress;
|
||||
|
||||
/**
|
||||
* hidden amount of pin/pattern/password bouncer
|
||||
@@ -88,7 +92,8 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
|
||||
@NonNull KeyguardStateController keyguardStateController,
|
||||
@NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
|
||||
@NonNull SystemUIDialogManager systemUIDialogManager,
|
||||
@NonNull UdfpsController udfpsController) {
|
||||
@NonNull UdfpsController udfpsController,
|
||||
@NonNull ActivityLaunchAnimator activityLaunchAnimator) {
|
||||
super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager,
|
||||
dumpManager);
|
||||
mKeyguardViewManager = statusBarKeyguardViewManager;
|
||||
@@ -99,6 +104,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
|
||||
mKeyguardStateController = keyguardStateController;
|
||||
mUdfpsController = udfpsController;
|
||||
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
|
||||
mActivityLaunchAnimator = activityLaunchAnimator;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,6 +142,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
|
||||
mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
|
||||
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this);
|
||||
mUnlockedScreenOffAnimationController.addCallback(mUnlockedScreenOffCallback);
|
||||
mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,6 +160,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
|
||||
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
|
||||
}
|
||||
mUnlockedScreenOffAnimationController.removeCallback(mUnlockedScreenOffCallback);
|
||||
mActivityLaunchAnimator.removeListener(mActivityLaunchAnimatorListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -294,6 +302,12 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
|
||||
0f, 255f);
|
||||
if (!mShowingUdfpsBouncer) {
|
||||
alpha *= (1.0f - mTransitionToFullShadeProgress);
|
||||
|
||||
// Fade out the icon if we are animating an activity launch over the lockscreen and the
|
||||
// activity didn't request the UDFPS.
|
||||
if (mIsLaunchingActivity && !mUdfpsRequested) {
|
||||
alpha *= (1.0f - mActivityLaunchProgress);
|
||||
}
|
||||
}
|
||||
mView.setUnpausedAlpha(alpha);
|
||||
}
|
||||
@@ -426,4 +440,26 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
|
||||
|
||||
private final UnlockedScreenOffAnimationController.Callback mUnlockedScreenOffCallback =
|
||||
(linear, eased) -> mStateListener.onDozeAmountChanged(linear, eased);
|
||||
|
||||
private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener =
|
||||
new ActivityLaunchAnimator.Listener() {
|
||||
@Override
|
||||
public void onLaunchAnimationStart() {
|
||||
mIsLaunchingActivity = true;
|
||||
mActivityLaunchProgress = 0f;
|
||||
updateAlpha();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLaunchAnimationEnd() {
|
||||
mIsLaunchingActivity = false;
|
||||
updateAlpha();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLaunchAnimationProgress(float linearProgress) {
|
||||
mActivityLaunchProgress = linearProgress;
|
||||
updateAlpha();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1412,7 +1412,8 @@ public class StatusBar extends CoreStartable implements
|
||||
|
||||
private void setUpPresenter() {
|
||||
// Set up the initial notification state.
|
||||
mActivityLaunchAnimator.setCallback(mKeyguardHandler);
|
||||
mActivityLaunchAnimator.setCallback(mActivityLaunchAnimatorCallback);
|
||||
mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
|
||||
mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
|
||||
mNotificationShadeWindowViewController,
|
||||
mStackScrollerController.getNotificationListContainer(),
|
||||
@@ -4393,7 +4394,7 @@ public class StatusBar extends CoreStartable implements
|
||||
}
|
||||
};
|
||||
|
||||
private final ActivityLaunchAnimator.Callback mKeyguardHandler =
|
||||
private final ActivityLaunchAnimator.Callback mActivityLaunchAnimatorCallback =
|
||||
new ActivityLaunchAnimator.Callback() {
|
||||
@Override
|
||||
public boolean isOnKeyguard() {
|
||||
@@ -4411,11 +4412,6 @@ public class StatusBar extends CoreStartable implements
|
||||
mMainExecutor.execute(() -> mKeyguardViewMediator.hideWithAnimation(runner));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlursDisabledForAppLaunch(boolean disabled) {
|
||||
mKeyguardViewMediator.setBlursDisabledForAppLaunch(disabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBackgroundColor(TaskInfo task) {
|
||||
if (!mStartingSurfaceOptional.isPresent()) {
|
||||
@@ -4427,6 +4423,19 @@ public class StatusBar extends CoreStartable implements
|
||||
}
|
||||
};
|
||||
|
||||
private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener =
|
||||
new ActivityLaunchAnimator.Listener() {
|
||||
@Override
|
||||
public void onLaunchAnimationStart() {
|
||||
mKeyguardViewMediator.setBlursDisabledForAppLaunch(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLaunchAnimationEnd() {
|
||||
mKeyguardViewMediator.setBlursDisabledForAppLaunch(false);
|
||||
}
|
||||
};
|
||||
|
||||
private final DemoMode mDemoModeCallback = new DemoMode() {
|
||||
@Override
|
||||
public void onDemoModeFinished() {
|
||||
|
||||
@@ -21,13 +21,12 @@ import android.widget.LinearLayout
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import junit.framework.Assert.assertFalse
|
||||
import junit.framework.Assert.assertNotNull
|
||||
import junit.framework.Assert.assertNull
|
||||
import junit.framework.Assert.assertTrue
|
||||
import junit.framework.AssertionFailedError
|
||||
import kotlin.concurrent.thread
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -40,6 +39,7 @@ import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Spy
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@@ -48,6 +48,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
|
||||
private val launchContainer = LinearLayout(mContext)
|
||||
private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
|
||||
@Mock lateinit var callback: ActivityLaunchAnimator.Callback
|
||||
@Mock lateinit var listener: ActivityLaunchAnimator.Listener
|
||||
@Spy private val controller = TestLaunchAnimatorController(launchContainer)
|
||||
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
|
||||
@Mock lateinit var failHandler: Log.TerribleFailureHandler
|
||||
@@ -59,6 +60,12 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
|
||||
fun setup() {
|
||||
activityLaunchAnimator = ActivityLaunchAnimator(launchAnimator)
|
||||
activityLaunchAnimator.callback = callback
|
||||
activityLaunchAnimator.addListener(listener)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
activityLaunchAnimator.removeListener(listener)
|
||||
}
|
||||
|
||||
private fun startIntentWithAnimation(
|
||||
@@ -177,7 +184,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
|
||||
val runner = activityLaunchAnimator.createRunner(controller)
|
||||
runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback)
|
||||
waitForIdleSync()
|
||||
verify(callback).setBlursDisabledForAppLaunch(eq(true))
|
||||
verify(listener).onLaunchAnimationStart()
|
||||
verify(controller).onLaunchAnimationStart(anyBoolean())
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import androidx.test.filters.SmallTest
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||
import com.android.systemui.statusbar.LockscreenShadeTransitionController
|
||||
@@ -89,6 +90,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
|
||||
@Mock private lateinit var udfpsController: UdfpsController
|
||||
@Mock private lateinit var udfpsView: UdfpsView
|
||||
@Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
|
||||
@Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
|
||||
|
||||
private val sensorProps = SensorLocationInternal("", 10, 100, 20)
|
||||
.asFingerprintSensorProperties()
|
||||
@@ -118,7 +120,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
|
||||
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
|
||||
configurationController, systemClock, keyguardStateController,
|
||||
unlockedScreenOffAnimationController, sensorProps, hbmProvider, reason,
|
||||
controllerCallback, onTouch)
|
||||
controllerCallback, onTouch, activityLaunchAnimator)
|
||||
block()
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ import com.android.internal.util.LatencyTracker;
|
||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.animation.ActivityLaunchAnimator;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.keyguard.ScreenLifecycle;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
@@ -180,6 +181,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
|
||||
private TypedArray mBrightnessBacklight;
|
||||
@Mock
|
||||
private SystemUIDialogManager mSystemUIDialogManager;
|
||||
@Mock
|
||||
private ActivityLaunchAnimator mActivityLaunchAnimator;
|
||||
|
||||
// Capture listeners so that they can be used to send events
|
||||
@Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
|
||||
@@ -252,7 +255,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
|
||||
mSystemClock,
|
||||
mUnlockedScreenOffAnimationController,
|
||||
mSystemUIDialogManager,
|
||||
mLatencyTracker);
|
||||
mLatencyTracker,
|
||||
mActivityLaunchAnimator);
|
||||
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
|
||||
mOverlayController = mOverlayCaptor.getValue();
|
||||
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
|
||||
|
||||
@@ -36,6 +36,7 @@ import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.animation.ActivityLaunchAnimator;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.keyguard.KeyguardViewMediator;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
@@ -96,6 +97,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
|
||||
private SystemUIDialogManager mDialogManager;
|
||||
@Mock
|
||||
private UdfpsController mUdfpsController;
|
||||
@Mock
|
||||
private ActivityLaunchAnimator mActivityLaunchAnimator;
|
||||
private FakeSystemClock mSystemClock = new FakeSystemClock();
|
||||
|
||||
private UdfpsKeyguardViewController mController;
|
||||
@@ -134,7 +137,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
|
||||
mKeyguardStateController,
|
||||
mUnlockedScreenOffAnimationController,
|
||||
mDialogManager,
|
||||
mUdfpsController);
|
||||
mUdfpsController,
|
||||
mActivityLaunchAnimator);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user