From 552f933e9d82cc2b6fa3845386acd17fd9948018 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Tue, 21 Nov 2023 13:59:34 +0100 Subject: [PATCH 1/4] [Android] Add support for the PlatformChannel "Share.invoke" command --- .../systemchannels/PlatformChannel.java | 12 +++++++ .../plugin/platform/PlatformPlugin.java | 15 ++++++++ .../systemchannels/PlatformChannelTest.java | 24 +++++++++++++ .../plugin/platform/PlatformPluginTest.java | 36 +++++++++++++++++++ 4 files changed, 87 insertions(+) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java index 06244b5b32f68..d3fd6774fa6f7 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java @@ -187,6 +187,11 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result.success(response); break; } + case "Share.invoke": + String text = (String) arguments; + platformMessageHandler.share(text); + result.success(null); + break; default: result.notImplemented(); break; @@ -549,6 +554,13 @@ default void setFrameworkHandlesBack(boolean frameworkHandlesBack) {} * can be pasted. */ boolean clipboardHasStrings(); + + /** + * The Flutter application would like to share the given {@code text} using the Android standard + * intent action named {@code Intent.ACTION_SEND}. See: + * https://developer.android.com/reference/android/content/Intent.html#ACTION_SEND + */ + void share(@NonNull String text); } /** Types of sounds the Android OS can play on behalf of an application. */ diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java index c3c14dff25213..e99f00df3a9d1 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java @@ -12,6 +12,7 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.res.AssetFileDescriptor; +import android.content.Intent; import android.os.Build; import android.view.HapticFeedbackConstants; import android.view.SoundEffectConstants; @@ -145,6 +146,11 @@ public void setClipboardData(@NonNull String text) { public boolean clipboardHasStrings() { return PlatformPlugin.this.clipboardHasStrings(); } + + @Override + public void share(@NonNull String text) { + PlatformPlugin.this.share(text); + } }; public PlatformPlugin(@NonNull Activity activity, @NonNull PlatformChannel platformChannel) { @@ -570,4 +576,13 @@ private boolean clipboardHasStrings() { } return description.hasMimeType("text/*"); } + + private void share(@NonNull String text) { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TEXT, text); + + activity.startActivity(Intent.createChooser(intent, null)); + } } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java index 85ebafd471ff8..31f594492e2fc 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java @@ -1,6 +1,8 @@ package io.flutter.embedding.engine.systemchannels; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.refEq; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -15,6 +17,7 @@ import org.json.JSONObject; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.robolectric.annotation.Config; @Config(manifest = Config.NONE) @@ -42,4 +45,25 @@ public void platformChannel_hasStringsMessage() { } verify(mockResult).success(refEq(expected)); } + + @Test + public void platformChannel_shareInvokeMessage() { + MethodChannel rawChannel = mock(MethodChannel.class); + FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); + PlatformChannel fakePlatformChannel = new PlatformChannel(dartExecutor); + PlatformChannel.PlatformMessageHandler mockMessageHandler = + mock(PlatformChannel.PlatformMessageHandler.class); + fakePlatformChannel.setPlatformMessageHandler(mockMessageHandler); + + ArgumentCaptor valueCapture = ArgumentCaptor.forClass(String.class); + doNothing().when(mockMessageHandler).share(valueCapture.capture()); + + MethodCall methodCall = new MethodCall("Share.invoke", "Flutter"); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult); + + assertEquals("Flutter", valueCapture.getValue()); + verify(mockResult).success(null); + } } diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java index 51e66c91db30b..fde7829bd7074 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java @@ -6,16 +6,19 @@ import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -29,6 +32,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; +import android.content.Intent; import android.net.Uri; import android.os.Build; import android.view.View; @@ -47,6 +51,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.android.controller.ActivityController; @@ -629,4 +635,34 @@ public void performsDefaultBehaviorWhenNoDelegateProvided() { verify(mockActivity, times(1)).finish(); } + + @Test + public void startChoosenActivityWhenSharingText() { + Activity mockActivity = mock(Activity.class); + PlatformChannel mockPlatformChannel = mock(PlatformChannel.class); + PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class); + PlatformPlugin platformPlugin = + new PlatformPlugin(mockActivity, mockPlatformChannel, mockPlatformPluginDelegate); + + // Mock Intent.createChooser (in real application it opens a chooser where the user can + // select which application will be used to share the selected text). + Intent choosenIntent = new Intent(); + MockedStatic intentClass = mockStatic(Intent.class); + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + intentClass + .when(() -> Intent.createChooser(intentCaptor.capture(), any())) + .thenReturn(choosenIntent); + + platformPlugin.mPlatformMessageHandler.share("Flutter"); + + // Activity.startActivity should have been called. + verify(mockActivity, times(1)).startActivity(choosenIntent); + + // The intent action created by the plugin and passed to Intent.createChooser should be + // 'Intent.ACTION_SEND'. + Intent sendToIntent = intentCaptor.getValue(); + assertEquals(sendToIntent.getAction(), Intent.ACTION_SEND); + assertEquals(sendToIntent.getType(), "text/plain"); + assertEquals(sendToIntent.getStringExtra(Intent.EXTRA_TEXT), "Flutter"); + } } From 1cedcf1e1a64d68355d622aad83aafdf85978027 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Wed, 29 Nov 2023 16:15:10 +0100 Subject: [PATCH 2/4] Introduce 'expectedContent' variable in test --- .../embedding/engine/systemchannels/PlatformChannelTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java index 31f594492e2fc..a8c203d917ffe 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java @@ -59,11 +59,12 @@ public void platformChannel_shareInvokeMessage() { ArgumentCaptor valueCapture = ArgumentCaptor.forClass(String.class); doNothing().when(mockMessageHandler).share(valueCapture.capture()); - MethodCall methodCall = new MethodCall("Share.invoke", "Flutter"); + final String expectedContent = "Flutter"; + MethodCall methodCall = new MethodCall("Share.invoke", expectedContent); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult); - assertEquals("Flutter", valueCapture.getValue()); + assertEquals(valueCapture.getValue(), expectedContent); verify(mockResult).success(null); } } From e169ef07d5ddb0f51248f347a9e37cb784d362a4 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Wed, 29 Nov 2023 20:05:02 +0100 Subject: [PATCH 3/4] Resolve merge conflicts --- .../android/io/flutter/plugin/platform/PlatformPlugin.java | 2 +- .../test/io/flutter/plugin/platform/PlatformPluginTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java index e99f00df3a9d1..58618b2b83247 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java @@ -11,8 +11,8 @@ import android.content.ClipDescription; import android.content.ClipboardManager; import android.content.Context; -import android.content.res.AssetFileDescriptor; import android.content.Intent; +import android.content.res.AssetFileDescriptor; import android.os.Build; import android.view.HapticFeedbackConstants; import android.view.SoundEffectConstants; diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java index fde7829bd7074..557d9f24048a8 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java @@ -31,8 +31,8 @@ import android.content.ClipboardManager; import android.content.ContentResolver; import android.content.Context; -import android.content.res.AssetFileDescriptor; import android.content.Intent; +import android.content.res.AssetFileDescriptor; import android.net.Uri; import android.os.Build; import android.view.View; From 0786fe95279689170790cb40e65be1b412aaa42c Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Wed, 29 Nov 2023 21:06:30 +0100 Subject: [PATCH 4/4] Use 'expectedContent' variable in the second test --- .../test/io/flutter/plugin/platform/PlatformPluginTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java index 557d9f24048a8..a9ced69401eee 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java @@ -653,7 +653,8 @@ public void startChoosenActivityWhenSharingText() { .when(() -> Intent.createChooser(intentCaptor.capture(), any())) .thenReturn(choosenIntent); - platformPlugin.mPlatformMessageHandler.share("Flutter"); + final String expectedContent = "Flutter"; + platformPlugin.mPlatformMessageHandler.share(expectedContent); // Activity.startActivity should have been called. verify(mockActivity, times(1)).startActivity(choosenIntent); @@ -663,6 +664,6 @@ public void startChoosenActivityWhenSharingText() { Intent sendToIntent = intentCaptor.getValue(); assertEquals(sendToIntent.getAction(), Intent.ACTION_SEND); assertEquals(sendToIntent.getType(), "text/plain"); - assertEquals(sendToIntent.getStringExtra(Intent.EXTRA_TEXT), "Flutter"); + assertEquals(sendToIntent.getStringExtra(Intent.EXTRA_TEXT), expectedContent); } }