From eb87dfe80a1af61f80eec103b760ae533d4aef75 Mon Sep 17 00:00:00 2001 From: Miranda Kephart Date: Thu, 2 Feb 2023 19:54:52 +0000 Subject: [PATCH 1/2] Fix legacy clipboard controller tests Bug: 260707686 Test: atest Change-Id: Id66f297191c103d079bf5b2e188d75995090be9c --- .../ClipboardOverlayControllerTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index 0ac26676a9c70..b05cbf9070cef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -140,6 +140,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @Test public void test_setClipData_nullData_legacy() { + mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); ClipData clipData = null; mOverlayController.setClipDataLegacy(clipData, ""); @@ -150,6 +151,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @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(""))); @@ -162,6 +164,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @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 +175,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 +191,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 +202,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 +213,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 +224,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 +237,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 +248,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 +258,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 +269,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 +283,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); From e9e8071989257f98a90e4c9921da0e6d22c6154e Mon Sep 17 00:00:00 2001 From: Miranda Kephart Date: Mon, 6 Feb 2023 13:05:35 -0500 Subject: [PATCH 2/2] Make all copied URIs shareable We can only display thumbnails for images, but the sharesheet can handle other types of URIs. Add the share button for these types of copied data. Also switches to not show UI if the clipData is null, and just show a toast if the clipboard contents have no items. (These cases should be approximately unreachable.) Bug: 268057213 Fix: 268057213 Test: atest Change-Id: I4c1e118c38c48c8ea8fd0d89ee426d7d961460af --- .../clipboardoverlay/ClipboardListener.java | 5 +- .../clipboardoverlay/ClipboardModel.kt | 51 +++++++++++------- .../ClipboardOverlayController.java | 15 +++--- .../ClipboardOverlayUtils.java | 17 ++++++ .../ClipboardListenerTest.java | 29 +++++++++++ .../clipboardoverlay/ClipboardModelTest.kt | 18 ++----- .../ClipboardOverlayControllerTest.java | 52 ++++++++++--------- 7 files changed, 120 insertions(+), 67 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index 82bb7237cab02..edda87527b1de 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -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(); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt index c7aaf09d65519..789833c6d8499 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt @@ -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 } } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index b41f30844e274..870378a416ca8 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -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 = - mClipboardUtils.getAction(model.getItem(), model.getSource()); + Optional 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(); } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java index 785e4a0743e40..a85f8b9357f51 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java @@ -65,6 +65,23 @@ class ClipboardOverlayUtils { return false; } + public Optional 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 getActions(CharSequence text, TextLinks textLinks) { + ArrayList 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 getAction(ClipData.Item item, String source) { return getActions(item).stream().filter(remoteAction -> { ComponentName component = remoteAction.getActionIntent().getIntent().getComponent(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java index 7177919909f9a..fd6e31ba3beed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java @@ -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); diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt index faef35e7bfcbb..c0dada4725b88 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt @@ -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) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index b05cbf9070cef..2099281d694ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -138,17 +138,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mOverlayController.hideImmediate(); } - @Test - public void test_setClipData_nullData_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - 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); @@ -158,7 +147,20 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { 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(); } @@ -308,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"}, @@ -326,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(); } @@ -442,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() { @Override