summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
author Andrey Epin <ayepin@google.com> 2023-02-03 08:56:17 -0800
committer Andrey Epin <ayepin@google.com> 2023-02-03 19:12:49 -0800
commit89697b484befb174482c92f5072f01836678d46c (patch)
tree793d96affef382ec314b0d044d628f8567068c76 /java
parent651ea96e4e069309cf3d1c5f518388e1410d5172 (diff)
Add optional text exclusion from media + text share
Add an option to the user to exclude text from being shared from a media + text sharing case. The text is ecluded only if the primary send inten is used i.e. it is possible for senders to specify alterntive intents that can also be used. Bug: 262277421 Test: manual testing of: (1) regualr image sharing, (2) image and text/web-link sharing, (3) using of an alternative intent, (4) sharing to a shortcut. All of those for one and two (+work) profiles. Test: integration tests Change-Id: Iebb33b951853c6588157fa92d3826b6a6e096591
Diffstat (limited to 'java')
-rw-r--r--java/res/layout/chooser_grid_preview_image.xml8
-rw-r--r--java/res/values/strings.xml9
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java30
-rw-r--r--java/src/com/android/intentresolver/ChooserContentPreviewUi.java50
-rw-r--r--java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java5
-rw-r--r--java/src/com/android/intentresolver/chooser/MultiDisplayResolveInfo.java5
-rw-r--r--java/src/com/android/intentresolver/chooser/NotSelectableTargetInfo.java7
-rw-r--r--java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java6
-rw-r--r--java/src/com/android/intentresolver/chooser/TargetInfo.java6
-rw-r--r--java/tests/src/com/android/intentresolver/ResolverDataProvider.java27
-rw-r--r--java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java165
11 files changed, 309 insertions, 9 deletions
diff --git a/java/res/layout/chooser_grid_preview_image.xml b/java/res/layout/chooser_grid_preview_image.xml
index 6af0af11..80c12e6c 100644
--- a/java/res/layout/chooser_grid_preview_image.xml
+++ b/java/res/layout/chooser_grid_preview_image.xml
@@ -24,6 +24,14 @@
android:orientation="vertical"
android:background="?android:attr/colorBackground">
+ <CheckBox
+ android:id="@+id/include_text_action"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:layout_marginEnd="@dimen/chooser_edge_margin_normal"
+ android:visibility="gone" />
+
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 59179504..24604ed3 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -108,4 +108,13 @@
<string name="select_images">Select Images</string>
<!-- Tittle for a button. Launches client-provided content reselection action. -->
<string name="select_text">Select Text</string>
+
+ <!-- Title for a button. Excludes a text from the shared content (a media and a text). -->
+ <string name="exclude_text">Exclude text</string>
+ <!-- Title for a button. Adds back a (previously excluded) text into the shared content. -->
+ <string name="include_text">Include text</string>
+ <!-- Title for a button. Excludes a web link from the shared content (a media and a text). -->
+ <string name="exclude_link">Exclude link</string>
+ <!-- Title for a button. Adds back a (previously excluded) web link into the shared content. -->
+ <string name="include_link">Include link</string>
</resources>
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index e741b06c..3a7d4e68 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -262,6 +262,8 @@ public class ChooserActivity extends ResolverActivity implements
private final SparseArray<ProfileRecord> mProfileRecords = new SparseArray<>();
+ private boolean mExcludeSharedText = false;
+
public ChooserActivity() {}
@Override
@@ -771,6 +773,11 @@ public class ChooserActivity extends ResolverActivity implements
? null
: createReselectionRunnable(reselectionAction);
}
+
+ @Override
+ public Consumer<Boolean> getExcludeSharedTextAction() {
+ return (isExcluded) -> mExcludeSharedText = isExcluded;
+ }
};
ViewGroup layout = mChooserContentPreviewUi.displayContentPreview(
@@ -1196,6 +1203,7 @@ public class ChooserActivity extends ResolverActivity implements
}
}
updateModelAndChooserCounts(target);
+ maybeRemoveSharedText(target);
return super.onTargetSelected(target, alwaysCheck);
}
@@ -1384,6 +1392,27 @@ public class ChooserActivity extends ResolverActivity implements
mIsSuccessfullySelected = true;
}
+ private void maybeRemoveSharedText(@androidx.annotation.NonNull TargetInfo targetInfo) {
+ Intent targetIntent = targetInfo.getTargetIntent();
+ if (targetIntent == null) {
+ return;
+ }
+ Intent originalTargetIntent = new Intent(mChooserRequest.getTargetIntent());
+ // Our TargetInfo implementations add associated component to the intent, let's do the same
+ // for the sake of the comparison below.
+ if (targetIntent.getComponent() != null) {
+ originalTargetIntent.setComponent(targetIntent.getComponent());
+ }
+ // Use filterEquals as a way to check that the primary intent is in use (and not an
+ // alternative one). For example, an app is sharing an image and a link with mime type
+ // "image/png" and provides an alternative intent to share only the link with mime type
+ // "text/uri". Should there be a target that accepts only the latter, the alternative intent
+ // will be used and we don't want to exclude the link from it.
+ if (mExcludeSharedText && originalTargetIntent.filterEquals(targetIntent)) {
+ targetIntent.removeExtra(Intent.EXTRA_TEXT);
+ }
+ }
+
private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) {
// Send DS target impression info to AppPredictor, only when user chooses app share.
if (targetInfo.isChooserTargetInfo()) {
@@ -1451,6 +1480,7 @@ public class ChooserActivity extends ResolverActivity implements
+ " cannot match refined source intent " + matchingIntent);
} else {
TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
+ maybeRemoveSharedText(clonedTarget);
if (super.onTargetSelected(clonedTarget, false)) {
updateModelAndChooserCounts(clonedTarget);
finish();
diff --git a/java/src/com/android/intentresolver/ChooserContentPreviewUi.java b/java/src/com/android/intentresolver/ChooserContentPreviewUi.java
index 9bef3553..91abd9d0 100644
--- a/java/src/com/android/intentresolver/ChooserContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/ChooserContentPreviewUi.java
@@ -40,6 +40,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.animation.DecelerateInterpolator;
+import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
@@ -59,6 +60,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -100,6 +102,17 @@ public final class ChooserContentPreviewUi {
*/
@Nullable
Runnable getReselectionAction();
+
+ /**
+ * <p>
+ * Creates an exclude-text action that can be called when the user changes shared text
+ * status in the Media + Text preview.
+ * </p>
+ * <p>
+ * <code>true</code> argument value indicates that the text should be excluded.
+ * </p>
+ */
+ Consumer<Boolean> getExcludeSharedTextAction();
}
/**
@@ -222,7 +235,8 @@ public final class ChooserContentPreviewUi {
transitionElementStatusCallback,
contentResolver,
imageClassifier,
- actionRowLayout);
+ actionRowLayout,
+ actionFactory);
break;
case CONTENT_PREVIEW_FILE:
layout = displayFileContentPreview(
@@ -360,7 +374,8 @@ public final class ChooserContentPreviewUi {
TransitionElementStatusCallback transitionElementStatusCallback,
ContentResolver contentResolver,
ImageMimeTypeClassifier imageClassifier,
- @LayoutRes int actionRowLayout) {
+ @LayoutRes int actionRowLayout,
+ ActionFactory actionFactory) {
ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
R.layout.chooser_grid_preview_image, parent, false);
ImagePreviewView imagePreview = inflateImagePreviewView(contentPreviewLayout);
@@ -391,7 +406,8 @@ public final class ChooserContentPreviewUi {
setTextInImagePreviewVisibility(
contentPreviewLayout,
- targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT));
+ targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT),
+ actionFactory);
imagePreview.setTransitionElementStatusCallback(transitionElementStatusCallback);
imagePreview.setImages(imageUris, imageLoader);
@@ -399,20 +415,36 @@ public final class ChooserContentPreviewUi {
}
private void setTextInImagePreviewVisibility(
- ViewGroup contentPreview, CharSequence text) {
+ ViewGroup contentPreview, CharSequence text, ActionFactory actionFactory) {
int visibility = mFeatureFlagRepository.isEnabled(Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW)
&& !TextUtils.isEmpty(text)
? View.VISIBLE
: View.GONE;
- TextView textView = contentPreview
+ final TextView textView = contentPreview
.requireViewById(com.android.internal.R.id.content_preview_text);
+ CheckBox actionView = contentPreview
+ .requireViewById(R.id.include_text_action);
textView.setVisibility(visibility);
- int linkMask = visibility == View.VISIBLE && HttpUriMatcher.isHttpUri(text.toString())
- ? Linkify.WEB_URLS
- : 0;
- textView.setAutoLinkMask(linkMask);
+ boolean isLink = visibility == View.VISIBLE && HttpUriMatcher.isHttpUri(text.toString());
+ textView.setAutoLinkMask(isLink ? Linkify.WEB_URLS : 0);
textView.setText(text);
+
+ if (visibility == View.VISIBLE) {
+ final int[] actionLabels = isLink
+ ? new int[] { R.string.include_link, R.string.exclude_link }
+ : new int[] { R.string.include_text, R.string.exclude_text };
+ final Consumer<Boolean> shareTextAction = actionFactory.getExcludeSharedTextAction();
+ actionView.setChecked(true);
+ actionView.setText(actionLabels[1]);
+ shareTextAction.accept(false);
+ actionView.setOnCheckedChangeListener((view, isChecked) -> {
+ view.setText(actionLabels[isChecked ? 1 : 0]);
+ textView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
+ shareTextAction.accept(!isChecked);
+ });
+ }
+ actionView.setVisibility(visibility);
}
private static List<ActionRow.Action> createImagePreviewActions(
diff --git a/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java b/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
index 1b729c0e..4bbf59d8 100644
--- a/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
+++ b/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
@@ -224,6 +224,11 @@ public class DisplayResolveInfo implements TargetInfo {
return false;
}
+ @Override
+ public Intent getTargetIntent() {
+ return mResolvedIntent;
+ }
+
public boolean isSuspended() {
return mIsSuspended;
}
diff --git a/java/src/com/android/intentresolver/chooser/MultiDisplayResolveInfo.java b/java/src/com/android/intentresolver/chooser/MultiDisplayResolveInfo.java
index e4cec887..0d79e5d5 100644
--- a/java/src/com/android/intentresolver/chooser/MultiDisplayResolveInfo.java
+++ b/java/src/com/android/intentresolver/chooser/MultiDisplayResolveInfo.java
@@ -119,4 +119,9 @@ public class MultiDisplayResolveInfo extends DisplayResolveInfo {
public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
return mTargetInfos.get(mSelected).startAsUser(activity, options, user);
}
+
+ @Override
+ public Intent getTargetIntent() {
+ return mTargetInfos.get(mSelected).getTargetIntent();
+ }
}
diff --git a/java/src/com/android/intentresolver/chooser/NotSelectableTargetInfo.java b/java/src/com/android/intentresolver/chooser/NotSelectableTargetInfo.java
index d6333374..9a2c971f 100644
--- a/java/src/com/android/intentresolver/chooser/NotSelectableTargetInfo.java
+++ b/java/src/com/android/intentresolver/chooser/NotSelectableTargetInfo.java
@@ -16,6 +16,7 @@
package com.android.intentresolver.chooser;
+import android.annotation.Nullable;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
@@ -101,6 +102,12 @@ public abstract class NotSelectableTargetInfo extends ChooserTargetInfo {
return false;
}
+ @Nullable
+ @Override
+ public Intent getTargetIntent() {
+ return null;
+ }
+
public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
return false;
}
diff --git a/java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java b/java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java
index 3ab50175..ca778233 100644
--- a/java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java
+++ b/java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java
@@ -346,6 +346,12 @@ public final class SelectableTargetInfo extends ChooserTargetInfo {
return mActivityStarter.startAsUser(activity, options, user);
}
+ @Nullable
+ @Override
+ public Intent getTargetIntent() {
+ return mBaseIntentToSend;
+ }
+
@Override
public ResolveInfo getResolveInfo() {
return mResolveInfo;
diff --git a/java/src/com/android/intentresolver/chooser/TargetInfo.java b/java/src/com/android/intentresolver/chooser/TargetInfo.java
index 72dd1b0b..7dcf66b2 100644
--- a/java/src/com/android/intentresolver/chooser/TargetInfo.java
+++ b/java/src/com/android/intentresolver/chooser/TargetInfo.java
@@ -88,6 +88,12 @@ public interface TargetInfo {
Intent getResolvedIntent();
/**
+ * Get the target intent, the one that will be used with one of the <code>start</code> methods.
+ * @return the intent with target will be launced with.
+ */
+ @Nullable Intent getTargetIntent();
+
+ /**
* Get the resolved component name that represents this target. Note that this may not
* be the component that will be directly launched by calling one of the <code>start</code>
* methods provided; this is the component that will be credited with the launch. This may be
diff --git a/java/tests/src/com/android/intentresolver/ResolverDataProvider.java b/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
index fb928e09..6807bfd6 100644
--- a/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
+++ b/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
@@ -41,6 +41,14 @@ public class ResolverDataProvider {
createResolverIntent(i), createResolveInfo(i, UserHandle.USER_CURRENT));
}
+ static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfo(
+ ComponentName componentName, Intent intent) {
+ return new ResolverActivity.ResolvedComponentInfo(
+ componentName,
+ intent,
+ createResolveInfo(componentName, UserHandle.USER_CURRENT));
+ }
+
static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i) {
return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
createResolverIntent(i), createResolveInfo(i, USER_SOMEONE_ELSE));
@@ -64,6 +72,13 @@ public class ResolverDataProvider {
return resolveInfo;
}
+ public static ResolveInfo createResolveInfo(ComponentName componentName, int userId) {
+ final ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = createActivityInfo(componentName);
+ resolveInfo.targetUserId = userId;
+ return resolveInfo;
+ }
+
static ActivityInfo createActivityInfo(int i) {
ActivityInfo ai = new ActivityInfo();
ai.name = "activity_name" + i;
@@ -75,6 +90,18 @@ public class ResolverDataProvider {
return ai;
}
+ static ActivityInfo createActivityInfo(ComponentName componentName) {
+ ActivityInfo ai = new ActivityInfo();
+ ai.name = componentName.getClassName();
+ ai.packageName = componentName.getPackageName();
+ ai.enabled = true;
+ ai.exported = true;
+ ai.permission = null;
+ ai.applicationInfo = createApplicationInfo();
+ ai.applicationInfo.packageName = componentName.getPackageName();
+ return ai;
+ }
+
static ApplicationInfo createApplicationInfo() {
ApplicationInfo ai = new ApplicationInfo();
ai.name = "app_name";
diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java
index 249dca62..c90f0b63 100644
--- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java
+++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java
@@ -128,6 +128,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -756,6 +757,170 @@ public class UnbundledChooserActivityTest {
}
@Test
+ public void testImagePlusTextSharing_ExcludeText() {
+ ChooserActivityOverrideData.getInstance().featureFlagRepository =
+ new TestFeatureFlagRepository(
+ Collections.singletonMap(Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW, true));
+ Intent sendIntent = createSendImageIntent(
+ Uri.parse("android.resource://com.android.frameworks.coretests/"
+ + R.drawable.test320x240));
+ ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
+ ChooserActivityOverrideData.getInstance().isImageType = true;
+ sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google");
+
+ List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList(
+ ResolverDataProvider.createResolvedComponentInfo(
+ new ComponentName("org.imageviewer", "ImageTarget"),
+ sendIntent),
+ ResolverDataProvider.createResolvedComponentInfo(
+ new ComponentName("org.textviewer", "UriTarget"),
+ new Intent("VIEW_TEXT"))
+ );
+
+ when(
+ ChooserActivityOverrideData
+ .getInstance()
+ .resolverListController
+ .getResolversForIntent(
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(resolvedComponentInfos);
+
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
+
+ onView(withId(R.id.include_text_action))
+ .check(matches(isDisplayed()))
+ .perform(click());
+ waitForIdle();
+
+ AtomicReference<Intent> launchedIntentRef = new AtomicReference<>();
+ ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ launchedIntentRef.set(targetInfo.getTargetIntent());
+ return true;
+ };
+
+ onView(withText(resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
+ waitForIdle();
+ assertThat(launchedIntentRef.get().hasExtra(Intent.EXTRA_TEXT)).isFalse();
+ }
+
+ @Test
+ public void testImagePlusTextSharing_RemoveAndAddBackText() {
+ ChooserActivityOverrideData.getInstance().featureFlagRepository =
+ new TestFeatureFlagRepository(
+ Collections.singletonMap(Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW, true));
+ Intent sendIntent = createSendImageIntent(
+ Uri.parse("android.resource://com.android.frameworks.coretests/"
+ + R.drawable.test320x240));
+ ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
+ ChooserActivityOverrideData.getInstance().isImageType = true;
+ final String text = "https://google.com/search?q=google";
+ sendIntent.putExtra(Intent.EXTRA_TEXT, text);
+
+ List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList(
+ ResolverDataProvider.createResolvedComponentInfo(
+ new ComponentName("org.imageviewer", "ImageTarget"),
+ sendIntent),
+ ResolverDataProvider.createResolvedComponentInfo(
+ new ComponentName("org.textviewer", "UriTarget"),
+ new Intent("VIEW_TEXT"))
+ );
+
+ when(
+ ChooserActivityOverrideData
+ .getInstance()
+ .resolverListController
+ .getResolversForIntent(
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(resolvedComponentInfos);
+
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
+
+ onView(withId(R.id.include_text_action))
+ .check(matches(isDisplayed()))
+ .perform(click());
+ waitForIdle();
+ onView(withId(R.id.include_text_action))
+ .perform(click());
+ waitForIdle();
+
+ AtomicReference<Intent> launchedIntentRef = new AtomicReference<>();
+ ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ launchedIntentRef.set(targetInfo.getTargetIntent());
+ return true;
+ };
+
+ onView(withText(resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
+ waitForIdle();
+ assertThat(launchedIntentRef.get().getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(text);
+ }
+
+ @Test
+ public void testImagePlusTextSharing_TextExclusionDoesNotAffectAlternativeIntent() {
+ ChooserActivityOverrideData.getInstance().featureFlagRepository =
+ new TestFeatureFlagRepository(
+ Collections.singletonMap(Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW, true));
+ Intent sendIntent = createSendImageIntent(
+ Uri.parse("android.resource://com.android.frameworks.coretests/"
+ + R.drawable.test320x240));
+ ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
+ ChooserActivityOverrideData.getInstance().isImageType = true;
+ sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google");
+
+ Intent alternativeIntent = createSendTextIntent();
+ final String text = "alternative intent";
+ alternativeIntent.putExtra(Intent.EXTRA_TEXT, text);
+
+ List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList(
+ ResolverDataProvider.createResolvedComponentInfo(
+ new ComponentName("org.imageviewer", "ImageTarget"),
+ sendIntent),
+ ResolverDataProvider.createResolvedComponentInfo(
+ new ComponentName("org.textviewer", "UriTarget"),
+ alternativeIntent)
+ );
+
+ when(
+ ChooserActivityOverrideData
+ .getInstance()
+ .resolverListController
+ .getResolversForIntent(
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(resolvedComponentInfos);
+
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
+
+ onView(withId(R.id.include_text_action))
+ .check(matches(isDisplayed()))
+ .perform(click());
+ waitForIdle();
+
+ AtomicReference<Intent> launchedIntentRef = new AtomicReference<>();
+ ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ launchedIntentRef.set(targetInfo.getTargetIntent());
+ return true;
+ };
+
+ onView(withText(resolvedComponentInfos.get(1).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
+ waitForIdle();
+ assertThat(launchedIntentRef.get().getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(text);
+ }
+
+ @Test
public void copyTextToClipboard() throws Exception {
Intent sendIntent = createSendTextIntent();
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);