Merge "[RemoteInputView] move data fields to Controller" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
cb2836e240
@@ -507,12 +507,11 @@ public class NotificationRemoteInputManager implements Dumpable {
|
||||
Math.max(cx + cy, cx + (h - cy)),
|
||||
Math.max((w - cx) + cy, (w - cx) + (h - cy)));
|
||||
|
||||
riv.setRevealParameters(cx, cy, r);
|
||||
riv.setPendingIntent(pendingIntent);
|
||||
riv.getController().setRevealParams(new RemoteInputView.RevealParams(cx, cy, r));
|
||||
riv.getController().setPendingIntent(pendingIntent);
|
||||
riv.setRemoteInput(inputs, input, editedSuggestionInfo);
|
||||
riv.getController().setRemoteInput(input);
|
||||
riv.getController().setRemoteInputs(inputs);
|
||||
riv.getController().setEditedSuggestionInfo(editedSuggestionInfo);
|
||||
riv.focusAnimated();
|
||||
if (userMessageContent != null) {
|
||||
riv.setEditTextContent(userMessageContent);
|
||||
|
||||
@@ -413,7 +413,10 @@ public class NotificationContentView extends FrameLayout implements Notification
|
||||
if (mExpandedRemoteInput != null) {
|
||||
mExpandedRemoteInput.onNotificationUpdateOrReset();
|
||||
if (mExpandedRemoteInput.isActive()) {
|
||||
mPreviousExpandedRemoteInputIntent = mExpandedRemoteInput.getPendingIntent();
|
||||
if (mExpandedRemoteInputController != null) {
|
||||
mPreviousExpandedRemoteInputIntent =
|
||||
mExpandedRemoteInputController.getPendingIntent();
|
||||
}
|
||||
mCachedExpandedRemoteInput = mExpandedRemoteInput;
|
||||
mCachedExpandedRemoteInputViewController = mExpandedRemoteInputController;
|
||||
mExpandedRemoteInput.dispatchStartTemporaryDetach();
|
||||
@@ -460,7 +463,10 @@ public class NotificationContentView extends FrameLayout implements Notification
|
||||
if (mHeadsUpRemoteInput != null) {
|
||||
mHeadsUpRemoteInput.onNotificationUpdateOrReset();
|
||||
if (mHeadsUpRemoteInput.isActive()) {
|
||||
mPreviousHeadsUpRemoteInputIntent = mHeadsUpRemoteInput.getPendingIntent();
|
||||
if (mHeadsUpRemoteInputController != null) {
|
||||
mPreviousHeadsUpRemoteInputIntent =
|
||||
mHeadsUpRemoteInputController.getPendingIntent();
|
||||
}
|
||||
mCachedHeadsUpRemoteInput = mHeadsUpRemoteInput;
|
||||
mCachedHeadsUpRemoteInputViewController = mHeadsUpRemoteInputController;
|
||||
mHeadsUpRemoteInput.dispatchStartTemporaryDetach();
|
||||
@@ -961,14 +967,16 @@ public class NotificationContentView extends FrameLayout implements Notification
|
||||
|
||||
private void transferRemoteInputFocus(int visibleType) {
|
||||
if (visibleType == VISIBLE_TYPE_HEADSUP
|
||||
&& mHeadsUpRemoteInput != null
|
||||
&& (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive())) {
|
||||
mHeadsUpRemoteInput.stealFocusFrom(mExpandedRemoteInput);
|
||||
&& mHeadsUpRemoteInputController != null
|
||||
&& mExpandedRemoteInputController != null
|
||||
&& mExpandedRemoteInputController.isActive()) {
|
||||
mHeadsUpRemoteInputController.stealFocusFrom(mExpandedRemoteInputController);
|
||||
}
|
||||
if (visibleType == VISIBLE_TYPE_EXPANDED
|
||||
&& mExpandedRemoteInput != null
|
||||
&& (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive())) {
|
||||
mExpandedRemoteInput.stealFocusFrom(mHeadsUpRemoteInput);
|
||||
&& mExpandedRemoteInputController != null
|
||||
&& mHeadsUpRemoteInputController != null
|
||||
&& mHeadsUpRemoteInputController.isActive()) {
|
||||
mExpandedRemoteInputController.stealFocusFrom(mHeadsUpRemoteInputController);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1313,7 +1321,6 @@ public class NotificationContentView extends FrameLayout implements Notification
|
||||
// If we find a matching action in the new notification, focus, otherwise close.
|
||||
Notification.Action[] actions = entry.getSbn().getNotification().actions;
|
||||
if (existingPendingIntent != null) {
|
||||
result.mView.setPendingIntent(existingPendingIntent);
|
||||
result.mController.setPendingIntent(existingPendingIntent);
|
||||
}
|
||||
if (result.mController.updatePendingIntentFromActions(actions)) {
|
||||
|
||||
@@ -22,8 +22,6 @@ import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RemoteInput;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.ColorStateList;
|
||||
@@ -75,7 +73,6 @@ import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.RemoteInputController;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
|
||||
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
|
||||
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
|
||||
import com.android.systemui.statusbar.phone.LightBarController;
|
||||
@@ -111,21 +108,15 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
private ProgressBar mProgressBar;
|
||||
private ImageView mDelete;
|
||||
private ImageView mDeleteBg;
|
||||
// TODO(b/193539698): remove reveal param fields, turn them into parameters where needed
|
||||
private int mRevealCx;
|
||||
private int mRevealCy;
|
||||
private int mRevealR;
|
||||
private boolean mColorized;
|
||||
private int mTint;
|
||||
private boolean mResetting;
|
||||
@Nullable private RevealParams mRevealParams;
|
||||
|
||||
// TODO(b/193539698): move these to a Controller
|
||||
private RemoteInputController mController;
|
||||
private final UiEventLogger mUiEventLogger;
|
||||
private NotificationEntry mEntry;
|
||||
private PendingIntent mPendingIntent;
|
||||
private RemoteInput mRemoteInput;
|
||||
private RemoteInput[] mRemoteInputs;
|
||||
private boolean mRemoved;
|
||||
private NotificationViewWrapper mWrapper;
|
||||
|
||||
@@ -397,9 +388,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
// During removal, we get reattached and lose focus. Not hiding in that
|
||||
// case to prevent flicker.
|
||||
if (!mRemoved) {
|
||||
if (animate && mRevealR > 0) {
|
||||
Animator reveal = ViewAnimationUtils.createCircularReveal(
|
||||
this, mRevealCx, mRevealCy, mRevealR, 0);
|
||||
if (animate && mRevealParams != null && mRevealParams.radius > 0) {
|
||||
Animator reveal = mRevealParams.createCircularHideAnimator(this);
|
||||
reveal.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
|
||||
reveal.setDuration(StackStateAnimator.ANIMATION_DURATION_CLOSE_REMOTE_INPUT);
|
||||
reveal.addListener(new AnimatorListenerAdapter() {
|
||||
@@ -454,30 +444,12 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
mController.removeSpinning(mEntry.getKey(), mToken);
|
||||
}
|
||||
|
||||
public void setPendingIntent(PendingIntent pendingIntent) {
|
||||
mPendingIntent = pendingIntent;
|
||||
public void setHintText(CharSequence hintText) {
|
||||
mEditText.setHint(hintText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the remote input for this view.
|
||||
*
|
||||
* @param remoteInputs The remote inputs that need to be sent to the app.
|
||||
* @param remoteInput The remote input that needs to be activated.
|
||||
* @param editedSuggestionInfo The smart reply that should be inserted in the remote input, or
|
||||
* {@code null} if the user is not editing a smart reply.
|
||||
*/
|
||||
public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput,
|
||||
@Nullable EditedSuggestionInfo editedSuggestionInfo) {
|
||||
mRemoteInputs = remoteInputs;
|
||||
mRemoteInput = remoteInput;
|
||||
mEditText.setHint(mRemoteInput.getLabel());
|
||||
mEditText.setSupportedMimeTypes(remoteInput.getAllowedDataTypes());
|
||||
|
||||
mEntry.editedSuggestionInfo = editedSuggestionInfo;
|
||||
if (editedSuggestionInfo != null) {
|
||||
mEntry.remoteInputText = editedSuggestionInfo.originalText;
|
||||
mEntry.remoteInputAttachment = null;
|
||||
}
|
||||
public void setSupportedMimeTypes(Collection<String> mimeTypes) {
|
||||
mEditText.setSupportedMimeTypes(mimeTypes);
|
||||
}
|
||||
|
||||
/** Populates the text field of the remote input with the given content. */
|
||||
@@ -486,9 +458,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
}
|
||||
|
||||
public void focusAnimated() {
|
||||
if (getVisibility() != VISIBLE) {
|
||||
Animator animator = ViewAnimationUtils.createCircularReveal(
|
||||
this, mRevealCx, mRevealCy, 0, mRevealR);
|
||||
if (getVisibility() != VISIBLE && mRevealParams != null) {
|
||||
Animator animator = mRevealParams.createCircularRevealAnimator(this);
|
||||
animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
|
||||
animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
|
||||
animator.start();
|
||||
@@ -587,30 +558,12 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
return mEditText.isFocused() && mEditText.isEnabled();
|
||||
}
|
||||
|
||||
// TODO(b/193539698): move this to the controller
|
||||
public void stealFocusFrom(RemoteInputView other) {
|
||||
other.close();
|
||||
setPendingIntent(other.mPendingIntent);
|
||||
setRemoteInput(other.mRemoteInputs, other.mRemoteInput, mEntry.editedSuggestionInfo);
|
||||
setRevealParameters(other.mRevealCx, other.mRevealCy, other.mRevealR);
|
||||
getController().setPendingIntent(other.mPendingIntent);
|
||||
getController().setRemoteInput(other.mRemoteInput);
|
||||
getController().setRemoteInputs(other.mRemoteInputs);
|
||||
focus();
|
||||
}
|
||||
|
||||
public PendingIntent getPendingIntent() {
|
||||
return mPendingIntent;
|
||||
}
|
||||
|
||||
public void setRemoved() {
|
||||
mRemoved = true;
|
||||
}
|
||||
|
||||
public void setRevealParameters(int cx, int cy, int r) {
|
||||
mRevealCx = cx;
|
||||
mRevealCy = cy;
|
||||
mRevealR = r;
|
||||
public void setRevealParameters(@Nullable RevealParams revealParams) {
|
||||
mRevealParams = revealParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -938,4 +891,24 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class RevealParams {
|
||||
final int centerX;
|
||||
final int centerY;
|
||||
final int radius;
|
||||
|
||||
public RevealParams(int centerX, int centerY, int radius) {
|
||||
this.centerX = centerX;
|
||||
this.centerY = centerY;
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
Animator createCircularRevealAnimator(View view) {
|
||||
return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, radius, 0);
|
||||
}
|
||||
|
||||
Animator createCircularHideAnimator(View view) {
|
||||
return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, 0, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@ import com.android.systemui.R
|
||||
import com.android.systemui.statusbar.NotificationRemoteInputManager
|
||||
import com.android.systemui.statusbar.RemoteInputController
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo
|
||||
import com.android.systemui.statusbar.policy.RemoteInputView.NotificationRemoteInputEvent
|
||||
import com.android.systemui.statusbar.policy.RemoteInputView.RevealParams
|
||||
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -41,6 +43,8 @@ interface RemoteInputViewController {
|
||||
fun bind()
|
||||
fun unbind()
|
||||
|
||||
val isActive: Boolean
|
||||
|
||||
/**
|
||||
* A [NotificationRemoteInputManager.BouncerChecker] that will be used to determine if the
|
||||
* device needs to be unlocked before sending the RemoteInput.
|
||||
@@ -55,6 +59,14 @@ interface RemoteInputViewController {
|
||||
/** Other [RemoteInput]s from the notification associated with this Controller. */
|
||||
var remoteInputs: Array<RemoteInput>?
|
||||
|
||||
var revealParams: RevealParams?
|
||||
|
||||
/**
|
||||
* Sets the smart reply that should be inserted in the remote input, or `null` if the user is
|
||||
* not editing a smart reply.
|
||||
*/
|
||||
fun setEditedSuggestionInfo(info: EditedSuggestionInfo?)
|
||||
|
||||
/**
|
||||
* Tries to find an action in {@param actions} that matches the current pending intent
|
||||
* of this view and updates its state to that of the found action
|
||||
@@ -68,6 +80,19 @@ interface RemoteInputViewController {
|
||||
|
||||
/** Unregisters a listener previously registered via [addOnSendRemoteInputListener] */
|
||||
fun removeOnSendRemoteInputListener(listener: OnSendRemoteInputListener)
|
||||
|
||||
fun close()
|
||||
|
||||
fun focus()
|
||||
|
||||
fun stealFocusFrom(other: RemoteInputViewController) {
|
||||
other.close()
|
||||
remoteInput = other.remoteInput
|
||||
remoteInputs = other.remoteInputs
|
||||
revealParams = other.revealParams
|
||||
pendingIntent = other.pendingIntent
|
||||
focus()
|
||||
}
|
||||
}
|
||||
|
||||
/** Listener for send events */
|
||||
@@ -100,15 +125,41 @@ class RemoteInputViewControllerImpl @Inject constructor(
|
||||
|
||||
private var isBound = false
|
||||
|
||||
override var pendingIntent: PendingIntent? = null
|
||||
override var bouncerChecker: NotificationRemoteInputManager.BouncerChecker? = null
|
||||
|
||||
override var remoteInput: RemoteInput? = null
|
||||
set(value) {
|
||||
field = value
|
||||
value?.takeIf { isBound }?.let {
|
||||
view.setHintText(it.label)
|
||||
view.setSupportedMimeTypes(it.allowedDataTypes)
|
||||
}
|
||||
}
|
||||
|
||||
override var pendingIntent: PendingIntent? = null
|
||||
override var remoteInputs: Array<RemoteInput>? = null
|
||||
|
||||
override var revealParams: RevealParams? = null
|
||||
set(value) {
|
||||
field = value
|
||||
if (isBound) {
|
||||
view.setRevealParameters(value)
|
||||
}
|
||||
}
|
||||
|
||||
override val isActive: Boolean get() = view.isActive
|
||||
|
||||
override fun bind() {
|
||||
if (isBound) return
|
||||
isBound = true
|
||||
|
||||
// TODO: refreshUI method?
|
||||
remoteInput?.let {
|
||||
view.setHintText(it.label)
|
||||
view.setSupportedMimeTypes(it.allowedDataTypes)
|
||||
}
|
||||
view.setRevealParameters(revealParams)
|
||||
|
||||
view.addOnEditTextFocusChangedListener(onFocusChangeListener)
|
||||
view.addOnSendRemoteInputListener(onSendRemoteInputListener)
|
||||
}
|
||||
@@ -121,6 +172,14 @@ class RemoteInputViewControllerImpl @Inject constructor(
|
||||
view.removeOnSendRemoteInputListener(onSendRemoteInputListener)
|
||||
}
|
||||
|
||||
override fun setEditedSuggestionInfo(info: EditedSuggestionInfo?) {
|
||||
entry.editedSuggestionInfo = info
|
||||
if (info != null) {
|
||||
entry.remoteInputText = info.originalText
|
||||
entry.remoteInputAttachment = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun updatePendingIntentFromActions(actions: Array<Notification.Action>?): Boolean {
|
||||
actions ?: return false
|
||||
val current: Intent = pendingIntent?.intent ?: return false
|
||||
@@ -132,8 +191,7 @@ class RemoteInputViewControllerImpl @Inject constructor(
|
||||
pendingIntent = actionIntent
|
||||
remoteInput = input
|
||||
remoteInputs = inputs
|
||||
view.pendingIntent = actionIntent
|
||||
view.setRemoteInput(inputs, input, null /* editedSuggestionInfo */)
|
||||
setEditedSuggestionInfo(null)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -148,6 +206,14 @@ class RemoteInputViewControllerImpl @Inject constructor(
|
||||
onSendListeners.remove(listener)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
view.close()
|
||||
}
|
||||
|
||||
override fun focus() {
|
||||
view.focus()
|
||||
}
|
||||
|
||||
private val onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
|
||||
remoteInputQuickSettingsDisabler.setRemoteInputActive(hasFocus)
|
||||
}
|
||||
@@ -217,11 +283,12 @@ class RemoteInputViewControllerImpl @Inject constructor(
|
||||
* @return returns intent with granted URI permissions that should be used immediately
|
||||
*/
|
||||
private fun prepareRemoteInput(remoteInput: RemoteInput): Intent =
|
||||
if (entry.remoteInputAttachment == null) prepareRemoteInputFromText(remoteInput)
|
||||
else prepareRemoteInputFromData(
|
||||
remoteInput,
|
||||
entry.remoteInputMimeType,
|
||||
entry.remoteInputUri)
|
||||
if (entry.remoteInputAttachment == null)
|
||||
prepareRemoteInputFromText(remoteInput)
|
||||
else prepareRemoteInputFromData(
|
||||
remoteInput,
|
||||
entry.remoteInputMimeType,
|
||||
entry.remoteInputUri)
|
||||
|
||||
private fun prepareRemoteInputFromText(remoteInput: RemoteInput): Intent {
|
||||
val results = Bundle()
|
||||
@@ -232,11 +299,10 @@ class RemoteInputViewControllerImpl @Inject constructor(
|
||||
view.clearAttachment()
|
||||
entry.remoteInputUri = null
|
||||
entry.remoteInputMimeType = null
|
||||
if (entry.editedSuggestionInfo == null) {
|
||||
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT)
|
||||
} else {
|
||||
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE)
|
||||
}
|
||||
val resultSource = entry.editedSuggestionInfo
|
||||
?.let { RemoteInput.SOURCE_FREE_FORM_INPUT }
|
||||
?: RemoteInput.SOURCE_CHOICE
|
||||
RemoteInput.setResultsSource(fillInIntent, resultSource)
|
||||
return fillInIntent
|
||||
}
|
||||
|
||||
@@ -266,11 +332,10 @@ class RemoteInputViewControllerImpl @Inject constructor(
|
||||
entry.remoteInputText = fullText
|
||||
|
||||
// mirror prepareRemoteInputFromText for text input
|
||||
if (entry.editedSuggestionInfo == null) {
|
||||
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT)
|
||||
} else if (entry.remoteInputAttachment == null) {
|
||||
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE)
|
||||
}
|
||||
val resultSource = entry.editedSuggestionInfo
|
||||
?.let { RemoteInput.SOURCE_FREE_FORM_INPUT }
|
||||
?: RemoteInput.SOURCE_CHOICE
|
||||
RemoteInput.setResultsSource(fillInIntent, resultSource)
|
||||
return fillInIntent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,15 +114,13 @@ public class RemoteInputViewTest extends SysuiTestCase {
|
||||
mContext.unregisterReceiver(mReceiver);
|
||||
}
|
||||
|
||||
private void setTestPendingIntent(RemoteInputView view, RemoteInputViewController controller) {
|
||||
private void setTestPendingIntent(RemoteInputViewController controller) {
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
|
||||
new Intent(TEST_ACTION), PendingIntent.FLAG_MUTABLE);
|
||||
RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).build();
|
||||
RemoteInput[] inputs = {input};
|
||||
|
||||
view.setPendingIntent(pendingIntent);
|
||||
controller.setPendingIntent(pendingIntent);
|
||||
view.setRemoteInput(inputs, input, null /* editedSuggestionInfo */);
|
||||
controller.setRemoteInput(input);
|
||||
controller.setRemoteInputs(inputs);
|
||||
}
|
||||
@@ -137,7 +135,7 @@ public class RemoteInputViewTest extends SysuiTestCase {
|
||||
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
|
||||
RemoteInputViewController controller = bindController(view, row.getEntry());
|
||||
|
||||
setTestPendingIntent(view, controller);
|
||||
setTestPendingIntent(controller);
|
||||
|
||||
view.focus();
|
||||
|
||||
@@ -177,7 +175,7 @@ public class RemoteInputViewTest extends SysuiTestCase {
|
||||
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
|
||||
RemoteInputViewController controller = bindController(view, row.getEntry());
|
||||
|
||||
setTestPendingIntent(view, controller);
|
||||
setTestPendingIntent(controller);
|
||||
|
||||
view.focus();
|
||||
|
||||
@@ -235,7 +233,7 @@ public class RemoteInputViewTest extends SysuiTestCase {
|
||||
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
|
||||
RemoteInputViewController controller = bindController(view, row.getEntry());
|
||||
|
||||
setTestPendingIntent(view, controller);
|
||||
setTestPendingIntent(controller);
|
||||
|
||||
// Open view, send a reply
|
||||
view.focus();
|
||||
@@ -265,7 +263,7 @@ public class RemoteInputViewTest extends SysuiTestCase {
|
||||
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
|
||||
RemoteInputViewController controller = bindController(view, row.getEntry());
|
||||
|
||||
setTestPendingIntent(view, controller);
|
||||
setTestPendingIntent(controller);
|
||||
|
||||
// Open view, attach an image
|
||||
view.focus();
|
||||
|
||||
Reference in New Issue
Block a user