Merge changes I4c1e118c,Id66f2971 into tm-qpr-dev am: d6819393d2 am: 75e4894e8d
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21303438 Change-Id: Ie11d0178738117052e8d223d707b29577a7760cb Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -97,8 +97,9 @@ public class ClipboardListener implements
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isUserSetupComplete()) {
|
||||
// just show a toast, user should not access intents from this state
|
||||
if (!isUserSetupComplete() // user should not access intents from this state
|
||||
|| clipData == null // shouldn't happen, but just in case
|
||||
|| clipData.getItemCount() == 0) {
|
||||
if (shouldShowToast(clipData)) {
|
||||
mUiEventLogger.log(CLIPBOARD_TOAST_SHOWN, 0, clipSource);
|
||||
mClipboardToast.showCopiedToast();
|
||||
|
||||
@@ -19,19 +19,23 @@ import android.content.ClipData
|
||||
import android.content.ClipDescription.EXTRA_IS_SENSITIVE
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.util.Size
|
||||
import android.view.textclassifier.TextLinks
|
||||
import com.android.systemui.R
|
||||
import java.io.IOException
|
||||
|
||||
data class ClipboardModel(
|
||||
val clipData: ClipData?,
|
||||
val clipData: ClipData,
|
||||
val source: String,
|
||||
val type: Type = Type.OTHER,
|
||||
val item: ClipData.Item? = null,
|
||||
val isSensitive: Boolean = false,
|
||||
val isRemote: Boolean = false,
|
||||
val type: Type,
|
||||
val text: CharSequence?,
|
||||
val textLinks: TextLinks?,
|
||||
val uri: Uri?,
|
||||
val isSensitive: Boolean,
|
||||
val isRemote: Boolean,
|
||||
) {
|
||||
private var _bitmap: Bitmap? = null
|
||||
|
||||
@@ -41,17 +45,16 @@ data class ClipboardModel(
|
||||
}
|
||||
return source == other.source &&
|
||||
type == other.type &&
|
||||
item?.text == other.item?.text &&
|
||||
item?.uri == other.item?.uri &&
|
||||
text == other.text &&
|
||||
uri == other.uri &&
|
||||
isSensitive == other.isSensitive
|
||||
}
|
||||
|
||||
fun loadThumbnail(context: Context): Bitmap? {
|
||||
if (_bitmap == null && type == Type.IMAGE && item?.uri != null) {
|
||||
if (_bitmap == null && type == Type.IMAGE && uri != null) {
|
||||
try {
|
||||
val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
|
||||
_bitmap =
|
||||
context.contentResolver.loadThumbnail(item.uri, Size(size, size * 4), null)
|
||||
_bitmap = context.contentResolver.loadThumbnail(uri, Size(size, size * 4), null)
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Thumbnail loading failed!", e)
|
||||
}
|
||||
@@ -66,27 +69,34 @@ data class ClipboardModel(
|
||||
fun fromClipData(
|
||||
context: Context,
|
||||
utils: ClipboardOverlayUtils,
|
||||
clipData: ClipData?,
|
||||
clipData: ClipData,
|
||||
source: String
|
||||
): ClipboardModel {
|
||||
if (clipData == null || clipData.itemCount == 0) {
|
||||
return ClipboardModel(clipData, source)
|
||||
}
|
||||
val sensitive = clipData.description?.extras?.getBoolean(EXTRA_IS_SENSITIVE) ?: false
|
||||
val item = clipData.getItemAt(0)!!
|
||||
val type = getType(context, item)
|
||||
val remote = utils.isRemoteCopy(context, clipData, source)
|
||||
return ClipboardModel(clipData, source, type, item, sensitive, remote)
|
||||
return ClipboardModel(
|
||||
clipData,
|
||||
source,
|
||||
type,
|
||||
item.text,
|
||||
item.textLinks,
|
||||
item.uri,
|
||||
sensitive,
|
||||
remote
|
||||
)
|
||||
}
|
||||
|
||||
private fun getType(context: Context, item: ClipData.Item): Type {
|
||||
return if (!TextUtils.isEmpty(item.text)) {
|
||||
Type.TEXT
|
||||
} else if (
|
||||
item.uri != null &&
|
||||
context.contentResolver.getType(item.uri)?.startsWith("image") == true
|
||||
) {
|
||||
Type.IMAGE
|
||||
} else if (item.uri != null) {
|
||||
if (context.contentResolver.getType(item.uri)?.startsWith("image") == true) {
|
||||
Type.IMAGE
|
||||
} else {
|
||||
Type.URI
|
||||
}
|
||||
} else {
|
||||
Type.OTHER
|
||||
}
|
||||
@@ -96,6 +106,7 @@ data class ClipboardModel(
|
||||
enum class Type {
|
||||
TEXT,
|
||||
IMAGE,
|
||||
URI,
|
||||
OTHER
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,14 +309,14 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
|
||||
if ((mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) && model.isRemote())
|
||||
|| DeviceConfig.getBoolean(
|
||||
DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
|
||||
if (model.getItem().getTextLinks() != null) {
|
||||
if (model.getTextLinks() != null) {
|
||||
classifyText(model);
|
||||
}
|
||||
}
|
||||
if (model.isSensitive()) {
|
||||
mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
|
||||
} else {
|
||||
mView.showTextPreview(model.getItem().getText(), false);
|
||||
mView.showTextPreview(model.getText(), false);
|
||||
}
|
||||
mView.setEditAccessibilityAction(true);
|
||||
mOnPreviewTapped = this::editText;
|
||||
@@ -326,12 +326,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
|
||||
mView.showImagePreview(
|
||||
model.isSensitive() ? null : model.loadThumbnail(mContext));
|
||||
mView.setEditAccessibilityAction(true);
|
||||
mOnPreviewTapped = () -> editImage(model.getItem().getUri());
|
||||
mOnPreviewTapped = () -> editImage(model.getUri());
|
||||
} else {
|
||||
// image loading failed
|
||||
mView.showDefaultTextPreview();
|
||||
}
|
||||
break;
|
||||
case URI:
|
||||
case OTHER:
|
||||
mView.showDefaultTextPreview();
|
||||
break;
|
||||
@@ -371,8 +372,8 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
|
||||
|
||||
private void classifyText(ClipboardModel model) {
|
||||
mBgExecutor.execute(() -> {
|
||||
Optional<RemoteAction> remoteAction =
|
||||
mClipboardUtils.getAction(model.getItem(), model.getSource());
|
||||
Optional<RemoteAction> remoteAction = mClipboardUtils.getAction(
|
||||
model.getText(), model.getTextLinks(), model.getSource());
|
||||
if (model.equals(mClipboardModel)) {
|
||||
remoteAction.ifPresent(action -> {
|
||||
mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
|
||||
@@ -419,10 +420,10 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
|
||||
accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
|
||||
} else if (clipData.getItemAt(0).getUri() != null) {
|
||||
if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
|
||||
mOnShareTapped = () -> shareContent(clipData);
|
||||
mView.showShareChip();
|
||||
accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
|
||||
}
|
||||
mOnShareTapped = () -> shareContent(clipData);
|
||||
mView.showShareChip();
|
||||
} else {
|
||||
mView.showDefaultTextPreview();
|
||||
}
|
||||
|
||||
@@ -65,6 +65,23 @@ class ClipboardOverlayUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Optional<RemoteAction> getAction(CharSequence text, TextLinks textLinks, String source) {
|
||||
return getActions(text, textLinks).stream().filter(remoteAction -> {
|
||||
ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
|
||||
return component != null && !TextUtils.equals(source, component.getPackageName());
|
||||
}).findFirst();
|
||||
}
|
||||
|
||||
private ArrayList<RemoteAction> getActions(CharSequence text, TextLinks textLinks) {
|
||||
ArrayList<RemoteAction> actions = new ArrayList<>();
|
||||
for (TextLinks.TextLink link : textLinks.getLinks()) {
|
||||
TextClassification classification = mTextClassifier.classifyText(
|
||||
text, link.getStart(), link.getEnd(), null);
|
||||
actions.addAll(classification.getActions());
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
public Optional<RemoteAction> getAction(ClipData.Item item, String source) {
|
||||
return getActions(item).stream().filter(remoteAction -> {
|
||||
ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
|
||||
|
||||
@@ -51,6 +51,8 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
@SmallTest
|
||||
@@ -194,6 +196,33 @@ public class ClipboardListenerTest extends SysuiTestCase {
|
||||
verifyZeroInteractions(mOverlayControllerProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_nullClipData_showsNothing() {
|
||||
when(mClipboardManager.getPrimaryClip()).thenReturn(null);
|
||||
|
||||
mClipboardListener.start();
|
||||
mClipboardListener.onPrimaryClipChanged();
|
||||
|
||||
verifyZeroInteractions(mUiEventLogger);
|
||||
verifyZeroInteractions(mClipboardToast);
|
||||
verifyZeroInteractions(mOverlayControllerProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_emptyClipData_showsToast() {
|
||||
ClipDescription description = new ClipDescription("Test", new String[0]);
|
||||
ClipData noItems = new ClipData(description, new ArrayList<>());
|
||||
when(mClipboardManager.getPrimaryClip()).thenReturn(noItems);
|
||||
|
||||
mClipboardListener.start();
|
||||
mClipboardListener.onPrimaryClipChanged();
|
||||
|
||||
verify(mUiEventLogger, times(1)).log(
|
||||
ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
|
||||
verify(mClipboardToast, times(1)).showCopiedToast();
|
||||
verifyZeroInteractions(mOverlayControllerProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_minimizedLayoutFlagOff_usesLegacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
|
||||
@@ -52,18 +52,6 @@ class ClipboardModelTest : SysuiTestCase() {
|
||||
mSampleClipData = ClipData("Test", arrayOf("text/plain"), ClipData.Item("Test Item"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_nullClipData() {
|
||||
val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, null, "test source")
|
||||
assertNull(model.clipData)
|
||||
assertEquals("test source", model.source)
|
||||
assertEquals(ClipboardModel.Type.OTHER, model.type)
|
||||
assertNull(model.item)
|
||||
assertFalse(model.isSensitive)
|
||||
assertFalse(model.isRemote)
|
||||
assertNull(model.loadThumbnail(mContext))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_textClipData() {
|
||||
val source = "test source"
|
||||
@@ -71,7 +59,9 @@ class ClipboardModelTest : SysuiTestCase() {
|
||||
assertEquals(mSampleClipData, model.clipData)
|
||||
assertEquals(source, model.source)
|
||||
assertEquals(ClipboardModel.Type.TEXT, model.type)
|
||||
assertEquals(mSampleClipData.getItemAt(0), model.item)
|
||||
assertEquals(mSampleClipData.getItemAt(0).text, model.text)
|
||||
assertEquals(mSampleClipData.getItemAt(0).textLinks, model.textLinks)
|
||||
assertEquals(mSampleClipData.getItemAt(0).uri, model.uri)
|
||||
assertFalse(model.isSensitive)
|
||||
assertFalse(model.isRemote)
|
||||
assertNull(model.loadThumbnail(mContext))
|
||||
@@ -84,7 +74,7 @@ class ClipboardModelTest : SysuiTestCase() {
|
||||
b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
|
||||
description.extras = b
|
||||
val data = ClipData(description, mSampleClipData.getItemAt(0))
|
||||
val (_, _, _, _, sensitive) =
|
||||
val (_, _, _, _, _, _, sensitive) =
|
||||
ClipboardModel.fromClipData(mContext, mClipboardUtils, data, "")
|
||||
assertTrue(sensitive)
|
||||
}
|
||||
|
||||
@@ -138,30 +138,36 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
mOverlayController.hideImmediate();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_setClipData_nullData_legacy() {
|
||||
ClipData clipData = null;
|
||||
mOverlayController.setClipDataLegacy(clipData, "");
|
||||
|
||||
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
|
||||
verify(mClipboardOverlayView, times(0)).showShareChip();
|
||||
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_setClipData_invalidImageData_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
ClipData clipData = new ClipData("", new String[]{"image/png"},
|
||||
new ClipData.Item(Uri.parse("")));
|
||||
|
||||
mOverlayController.setClipDataLegacy(clipData, "");
|
||||
|
||||
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
|
||||
verify(mClipboardOverlayView, times(0)).showShareChip();
|
||||
verify(mClipboardOverlayView, times(1)).showShareChip();
|
||||
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_setClipData_nonImageUri_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
ClipData clipData = new ClipData("", new String[]{"resource/png"},
|
||||
new ClipData.Item(Uri.parse("")));
|
||||
|
||||
mOverlayController.setClipDataLegacy(clipData, "");
|
||||
|
||||
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
|
||||
verify(mClipboardOverlayView, times(1)).showShareChip();
|
||||
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_setClipData_textData_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
|
||||
mOverlayController.setClipDataLegacy(mSampleClipData, "");
|
||||
|
||||
verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
|
||||
@@ -171,6 +177,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void test_setClipData_sensitiveTextData_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
|
||||
ClipDescription description = mSampleClipData.getDescription();
|
||||
PersistableBundle b = new PersistableBundle();
|
||||
b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
|
||||
@@ -185,6 +193,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void test_setClipData_repeatedCalls_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
when(mAnimator.isRunning()).thenReturn(true);
|
||||
|
||||
mOverlayController.setClipDataLegacy(mSampleClipData, "");
|
||||
@@ -195,6 +204,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void test_viewCallbacks_onShareTapped_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
mOverlayController.setClipDataLegacy(mSampleClipData, "");
|
||||
|
||||
mCallbacks.onShareButtonTapped();
|
||||
@@ -205,6 +215,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void test_viewCallbacks_onDismissTapped_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
mOverlayController.setClipDataLegacy(mSampleClipData, "");
|
||||
|
||||
mCallbacks.onDismissButtonTapped();
|
||||
@@ -215,6 +226,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void test_multipleDismissals_dismissesOnce_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
|
||||
mCallbacks.onSwipeDismissInitiated(mAnimator);
|
||||
mCallbacks.onDismissButtonTapped();
|
||||
mCallbacks.onSwipeDismissInitiated(mAnimator);
|
||||
@@ -226,6 +239,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void test_remoteCopy_withFlagOn_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
|
||||
when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
|
||||
|
||||
@@ -236,6 +250,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void test_remoteCopy_withFlagOff_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
|
||||
|
||||
mOverlayController.setClipDataLegacy(mSampleClipData, "");
|
||||
@@ -245,6 +260,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void test_nonRemoteCopy_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
|
||||
when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
|
||||
|
||||
@@ -255,6 +271,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void test_logsUseLastClipSource_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
|
||||
mOverlayController.setClipDataLegacy(mSampleClipData, "first.package");
|
||||
mCallbacks.onDismissButtonTapped();
|
||||
mOverlayController.setClipDataLegacy(mSampleClipData, "second.package");
|
||||
@@ -267,6 +285,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void test_logOnClipboardActionsShown_legacy() {
|
||||
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
|
||||
ClipData.Item item = mSampleClipData.getItemAt(0);
|
||||
item.setTextLinks(Mockito.mock(TextLinks.class));
|
||||
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
|
||||
@@ -291,16 +310,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
// start of refactored setClipData tests
|
||||
@Test
|
||||
public void test_setClipData_nullData() {
|
||||
ClipData clipData = null;
|
||||
mOverlayController.setClipData(clipData, "");
|
||||
|
||||
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
|
||||
verify(mClipboardOverlayView, times(0)).showShareChip();
|
||||
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_setClipData_invalidImageData() {
|
||||
ClipData clipData = new ClipData("", new String[]{"image/png"},
|
||||
@@ -309,7 +318,19 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
mOverlayController.setClipData(clipData, "");
|
||||
|
||||
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
|
||||
verify(mClipboardOverlayView, times(0)).showShareChip();
|
||||
verify(mClipboardOverlayView, times(1)).showShareChip();
|
||||
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_setClipData_nonImageUri() {
|
||||
ClipData clipData = new ClipData("", new String[]{"resource/png"},
|
||||
new ClipData.Item(Uri.parse("")));
|
||||
|
||||
mOverlayController.setClipData(clipData, "");
|
||||
|
||||
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
|
||||
verify(mClipboardOverlayView, times(1)).showShareChip();
|
||||
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
|
||||
}
|
||||
|
||||
@@ -425,7 +446,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
|
||||
when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString()))
|
||||
.thenReturn(true);
|
||||
when(mClipboardUtils.getAction(any(ClipData.Item.class), anyString()))
|
||||
when(mClipboardUtils.getAction(any(CharSequence.class), any(TextLinks.class), anyString()))
|
||||
.thenReturn(Optional.of(Mockito.mock(RemoteAction.class)));
|
||||
when(mClipboardOverlayView.post(any(Runnable.class))).thenAnswer(new Answer<Object>() {
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user