summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Xin Li <delphij@google.com> 2023-08-31 11:53:07 -0700
committer Xin Li <delphij@google.com> 2023-08-31 12:31:11 -0700
commit70b8273014ff735ecf18aaeeaf6fa8c0f613fd49 (patch)
treed95426955fa9e901d023bb63c64fe7c037b8cf9e
parent80bfff1f0eef6db4e061d9892450737e110bad59 (diff)
parent398f5f236f5165448798a4216c814d03a9b93555 (diff)
Merge UP1A.230905.019
Merged-In: I26c6eef24e7f909842eb066a73286a7525124d21 Change-Id: I5073cf615f25b0c211518909d033ea53fe50244e
-rw-r--r--java/src/com/android/intentresolver/ChooserActionFactory.java81
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt152
2 files changed, 157 insertions, 76 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActionFactory.java b/java/src/com/android/intentresolver/ChooserActionFactory.java
index 6ec62753..06c7e8d7 100644
--- a/java/src/com/android/intentresolver/ChooserActionFactory.java
+++ b/java/src/com/android/intentresolver/ChooserActionFactory.java
@@ -78,12 +78,19 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+ // Boolean extra used to inform the editor that it may want to customize the editing experience
+ // for the sharesheet editing flow.
+ private static final String EDIT_SOURCE = "edit_source";
+ private static final String EDIT_SOURCE_SHARESHEET = "sharesheet";
+
private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label";
private static final String CHIP_ICON_METADATA_KEY = "android.service.chooser.chip_icon";
private static final String IMAGE_EDITOR_SHARED_ELEMENT = "screenshot_preview_image";
private final Context mContext;
+
+ @Nullable
private final Runnable mCopyButtonRunnable;
private final Runnable mEditButtonRunnable;
private final ImmutableList<ChooserAction> mCustomActions;
@@ -140,7 +147,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
@VisibleForTesting
ChooserActionFactory(
Context context,
- Runnable copyButtonRunnable,
+ @Nullable Runnable copyButtonRunnable,
Runnable editButtonRunnable,
List<ChooserAction> customActions,
@Nullable ChooserAction modifyShareAction,
@@ -219,49 +226,24 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
return mExcludeSharedTextAction;
}
+ @Nullable
private static Runnable makeCopyButtonRunnable(
Context context,
Intent targetIntent,
String referrerPackageName,
Consumer<Integer> finishCallback,
ChooserActivityLogger logger) {
+ final ClipData clipData;
+ try {
+ clipData = extractTextToCopy(targetIntent);
+ } catch (Throwable t) {
+ Log.e(TAG, "Failed to extract data to copy", t);
+ return null;
+ }
+ if (clipData == null) {
+ return null;
+ }
return () -> {
- if (targetIntent == null) {
- finishCallback.accept(null);
- return;
- }
-
- final String action = targetIntent.getAction();
-
- ClipData clipData = null;
- if (Intent.ACTION_SEND.equals(action)) {
- String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
- Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
-
- if (extraText != null) {
- clipData = ClipData.newPlainText(null, extraText);
- } else if (extraStream != null) {
- clipData = ClipData.newUri(context.getContentResolver(), null, extraStream);
- } else {
- Log.w(TAG, "No data available to copy to clipboard");
- return;
- }
- } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
- final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
- Intent.EXTRA_STREAM);
- clipData = ClipData.newUri(context.getContentResolver(), null, streams.get(0));
- for (int i = 1; i < streams.size(); i++) {
- clipData.addItem(
- context.getContentResolver(),
- new ClipData.Item(streams.get(i)));
- }
- } else {
- // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
- // so warn about unexpected action
- Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
- return;
- }
-
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(
Context.CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClipAsPackage(clipData, referrerPackageName);
@@ -271,6 +253,30 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
};
}
+ @Nullable
+ private static ClipData extractTextToCopy(Intent targetIntent) {
+ if (targetIntent == null) {
+ return null;
+ }
+
+ final String action = targetIntent.getAction();
+
+ ClipData clipData = null;
+ if (Intent.ACTION_SEND.equals(action)) {
+ String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
+
+ if (extraText != null) {
+ clipData = ClipData.newPlainText(null, extraText);
+ } else {
+ Log.w(TAG, "No data available to copy to clipboard");
+ }
+ } else {
+ // expected to only be visible with ACTION_SEND (when a text is shared)
+ Log.d(TAG, "Action (" + action + ") not supported for copying to clipboard");
+ }
+ return clipData;
+ }
+
private static TargetInfo getEditSharingTarget(
Context context,
Intent originalIntent,
@@ -284,6 +290,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
resolveIntent.setFlags(originalIntent.getFlags() & URI_PERMISSION_INTENT_FLAGS);
resolveIntent.setComponent(editorComponent);
resolveIntent.setAction(Intent.ACTION_EDIT);
+ resolveIntent.putExtra(EDIT_SOURCE, EDIT_SOURCE_SHARESHEET);
String originalAction = originalIntent.getAction();
if (Intent.ACTION_SEND.equals(originalAction)) {
if (resolveIntent.getData() == null) {
diff --git a/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt b/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt
index d72c9aa6..8d994f08 100644
--- a/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt
+++ b/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt
@@ -25,48 +25,45 @@ import android.content.IntentFilter
import android.content.res.Resources
import android.graphics.drawable.Icon
import android.service.chooser.ChooserAction
-import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.intentresolver.flags.FeatureFlagRepository
import com.google.common.collect.ImmutableList
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
-import java.util.concurrent.Callable
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
-import java.util.function.Consumer
@RunWith(AndroidJUnit4::class)
class ChooserActionFactoryTest {
private val context = InstrumentationRegistry.getInstrumentation().getContext()
private val logger = mock<ChooserActivityLogger>()
- private val flags = mock<FeatureFlagRepository>()
private val actionLabel = "Action label"
private val modifyShareLabel = "Modify share"
private val testAction = "com.android.intentresolver.testaction"
private val countdown = CountDownLatch(1)
- private val testReceiver: BroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- // Just doing at most a single countdown per test.
- countdown.countDown()
+ private val testReceiver: BroadcastReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ // Just doing at most a single countdown per test.
+ countdown.countDown()
+ }
}
- }
- private object resultConsumer : Consumer<Int> {
- var latestReturn = Integer.MIN_VALUE
+ private val resultConsumer =
+ object : Consumer<Int> {
+ var latestReturn = Integer.MIN_VALUE
- override fun accept(resultCode: Int) {
- latestReturn = resultCode
+ override fun accept(resultCode: Int) {
+ latestReturn = resultCode
+ }
}
- }
-
@Before
fun setup() {
context.registerReceiver(testReceiver, IntentFilter(testAction))
@@ -91,7 +88,7 @@ class ChooserActionFactoryTest {
Mockito.verify(logger).logCustomActionSelected(eq(0))
assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn)
- // Verify the pendingintent has been called
+ // Verify the pending intent has been called
countdown.await(500, TimeUnit.MILLISECONDS)
}
@@ -109,42 +106,119 @@ class ChooserActionFactoryTest {
val action = factory.modifyShareAction ?: error("Modify share action should not be null")
action.onClicked.run()
- Mockito.verify(logger).logActionSelected(
- eq(ChooserActivityLogger.SELECTION_TYPE_MODIFY_SHARE))
+ Mockito.verify(logger)
+ .logActionSelected(eq(ChooserActivityLogger.SELECTION_TYPE_MODIFY_SHARE))
assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn)
- // Verify the pendingintent has been called
+ // Verify the pending intent has been called
countdown.await(500, TimeUnit.MILLISECONDS)
}
+ @Test
+ fun nonSendAction_noCopyRunnable() {
+ val targetIntent =
+ Intent(Intent.ACTION_SEND_MULTIPLE).apply {
+ putExtra(Intent.EXTRA_TEXT, "Text to show")
+ }
+
+ val chooserRequest =
+ mock<ChooserRequestParameters> {
+ whenever(this.targetIntent).thenReturn(targetIntent)
+ whenever(chooserActions).thenReturn(ImmutableList.of())
+ }
+ val testSubject =
+ ChooserActionFactory(
+ context,
+ chooserRequest,
+ mock(),
+ logger,
+ {},
+ { null },
+ mock(),
+ {},
+ )
+ assertThat(testSubject.copyButtonRunnable).isNull()
+ }
+
+ @Test
+ fun sendActionNoText_noCopyRunnable() {
+ val targetIntent = Intent(Intent.ACTION_SEND)
+
+ val chooserRequest =
+ mock<ChooserRequestParameters> {
+ whenever(this.targetIntent).thenReturn(targetIntent)
+ whenever(chooserActions).thenReturn(ImmutableList.of())
+ }
+ val testSubject =
+ ChooserActionFactory(
+ context,
+ chooserRequest,
+ mock(),
+ logger,
+ {},
+ { null },
+ mock(),
+ {},
+ )
+ assertThat(testSubject.copyButtonRunnable).isNull()
+ }
+
+ @Test
+ fun sendActionWithText_nonNullCopyRunnable() {
+ val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_TEXT, "Text") }
+
+ val chooserRequest =
+ mock<ChooserRequestParameters> {
+ whenever(this.targetIntent).thenReturn(targetIntent)
+ whenever(chooserActions).thenReturn(ImmutableList.of())
+ }
+ val testSubject =
+ ChooserActionFactory(
+ context,
+ chooserRequest,
+ mock(),
+ logger,
+ {},
+ { null },
+ mock(),
+ {},
+ )
+ assertThat(testSubject.copyButtonRunnable).isNotNull()
+ }
+
private fun createFactory(includeModifyShare: Boolean = false): ChooserActionFactory {
- val testPendingIntent = PendingIntent.getActivity(context, 0, Intent(testAction),0)
+ val testPendingIntent = PendingIntent.getActivity(context, 0, Intent(testAction), 0)
val targetIntent = Intent()
- val action = ChooserAction.Builder(
- Icon.createWithResource("", Resources.ID_NULL),
- actionLabel,
- testPendingIntent
- ).build()
+ val action =
+ ChooserAction.Builder(
+ Icon.createWithResource("", Resources.ID_NULL),
+ actionLabel,
+ testPendingIntent
+ )
+ .build()
val chooserRequest = mock<ChooserRequestParameters>()
whenever(chooserRequest.targetIntent).thenReturn(targetIntent)
whenever(chooserRequest.chooserActions).thenReturn(ImmutableList.of(action))
if (includeModifyShare) {
- val modifyShare = ChooserAction.Builder(
- Icon.createWithResource("", Resources.ID_NULL),
- modifyShareLabel,
- testPendingIntent
- ).build()
+ val modifyShare =
+ ChooserAction.Builder(
+ Icon.createWithResource("", Resources.ID_NULL),
+ modifyShareLabel,
+ testPendingIntent
+ )
+ .build()
whenever(chooserRequest.modifyShareAction).thenReturn(modifyShare)
}
return ChooserActionFactory(
context,
chooserRequest,
- mock<ChooserIntegratedDeviceComponents>(),
+ mock(),
logger,
- Consumer<Boolean>{},
- Callable<View?>{null},
- mock<ChooserActionFactory.ActionActivityStarter>(),
- resultConsumer)
+ {},
+ { null },
+ mock(),
+ resultConsumer
+ )
}
-} \ No newline at end of file
+}