Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 1a9efa8

Browse files
authored
[Android] Add support for the PlatformChannel "Share.invoke" command (#48265)
## Description This PR adds support for the PlatformChannel `Share.invoke` message on Android (before this PR it is only supported on iOS). ## Related Issue Engine side for flutter/flutter#138728 ## Tests Adds 2 tests.
1 parent eb1b0e7 commit 1a9efa8

File tree

4 files changed

+89
-0
lines changed

4 files changed

+89
-0
lines changed

shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result
187187
result.success(response);
188188
break;
189189
}
190+
case "Share.invoke":
191+
String text = (String) arguments;
192+
platformMessageHandler.share(text);
193+
result.success(null);
194+
break;
190195
default:
191196
result.notImplemented();
192197
break;
@@ -549,6 +554,13 @@ default void setFrameworkHandlesBack(boolean frameworkHandlesBack) {}
549554
* can be pasted.
550555
*/
551556
boolean clipboardHasStrings();
557+
558+
/**
559+
* The Flutter application would like to share the given {@code text} using the Android standard
560+
* intent action named {@code Intent.ACTION_SEND}. See:
561+
* https://developer.android.com/reference/android/content/Intent.html#ACTION_SEND
562+
*/
563+
void share(@NonNull String text);
552564
}
553565

554566
/** Types of sounds the Android OS can play on behalf of an application. */

shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import android.content.ClipDescription;
1212
import android.content.ClipboardManager;
1313
import android.content.Context;
14+
import android.content.Intent;
1415
import android.content.res.AssetFileDescriptor;
1516
import android.os.Build;
1617
import android.view.HapticFeedbackConstants;
@@ -145,6 +146,11 @@ public void setClipboardData(@NonNull String text) {
145146
public boolean clipboardHasStrings() {
146147
return PlatformPlugin.this.clipboardHasStrings();
147148
}
149+
150+
@Override
151+
public void share(@NonNull String text) {
152+
PlatformPlugin.this.share(text);
153+
}
148154
};
149155

150156
public PlatformPlugin(@NonNull Activity activity, @NonNull PlatformChannel platformChannel) {
@@ -570,4 +576,13 @@ private boolean clipboardHasStrings() {
570576
}
571577
return description.hasMimeType("text/*");
572578
}
579+
580+
private void share(@NonNull String text) {
581+
Intent intent = new Intent();
582+
intent.setAction(Intent.ACTION_SEND);
583+
intent.setType("text/plain");
584+
intent.putExtra(Intent.EXTRA_TEXT, text);
585+
586+
activity.startActivity(Intent.createChooser(intent, null));
587+
}
573588
}

shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.flutter.embedding.engine.systemchannels;
22

3+
import static org.junit.Assert.assertEquals;
34
import static org.mockito.ArgumentMatchers.refEq;
5+
import static org.mockito.Mockito.doNothing;
46
import static org.mockito.Mockito.mock;
57
import static org.mockito.Mockito.verify;
68
import static org.mockito.Mockito.when;
@@ -15,6 +17,7 @@
1517
import org.json.JSONObject;
1618
import org.junit.Test;
1719
import org.junit.runner.RunWith;
20+
import org.mockito.ArgumentCaptor;
1821
import org.robolectric.annotation.Config;
1922

2023
@Config(manifest = Config.NONE)
@@ -42,4 +45,26 @@ public void platformChannel_hasStringsMessage() {
4245
}
4346
verify(mockResult).success(refEq(expected));
4447
}
48+
49+
@Test
50+
public void platformChannel_shareInvokeMessage() {
51+
MethodChannel rawChannel = mock(MethodChannel.class);
52+
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
53+
DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class));
54+
PlatformChannel fakePlatformChannel = new PlatformChannel(dartExecutor);
55+
PlatformChannel.PlatformMessageHandler mockMessageHandler =
56+
mock(PlatformChannel.PlatformMessageHandler.class);
57+
fakePlatformChannel.setPlatformMessageHandler(mockMessageHandler);
58+
59+
ArgumentCaptor<String> valueCapture = ArgumentCaptor.forClass(String.class);
60+
doNothing().when(mockMessageHandler).share(valueCapture.capture());
61+
62+
final String expectedContent = "Flutter";
63+
MethodCall methodCall = new MethodCall("Share.invoke", expectedContent);
64+
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
65+
fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult);
66+
67+
assertEquals(valueCapture.getValue(), expectedContent);
68+
verify(mockResult).success(null);
69+
}
4570
}

shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@
66

77
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
88
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
9+
import static org.junit.Assert.assertEquals;
910
import static org.junit.Assert.assertFalse;
1011
import static org.junit.Assert.assertNotNull;
1112
import static org.junit.Assert.assertNull;
1213
import static org.junit.Assert.assertTrue;
14+
import static org.mockito.Mockito.any;
1315
import static org.mockito.Mockito.anyBoolean;
1416
import static org.mockito.Mockito.anyString;
1517
import static org.mockito.Mockito.doReturn;
1618
import static org.mockito.Mockito.doThrow;
1719
import static org.mockito.Mockito.eq;
1820
import static org.mockito.Mockito.mock;
21+
import static org.mockito.Mockito.mockStatic;
1922
import static org.mockito.Mockito.never;
2023
import static org.mockito.Mockito.spy;
2124
import static org.mockito.Mockito.times;
@@ -28,6 +31,7 @@
2831
import android.content.ClipboardManager;
2932
import android.content.ContentResolver;
3033
import android.content.Context;
34+
import android.content.Intent;
3135
import android.content.res.AssetFileDescriptor;
3236
import android.net.Uri;
3337
import android.os.Build;
@@ -47,6 +51,8 @@
4751
import java.util.concurrent.atomic.AtomicBoolean;
4852
import org.junit.Test;
4953
import org.junit.runner.RunWith;
54+
import org.mockito.ArgumentCaptor;
55+
import org.mockito.MockedStatic;
5056
import org.robolectric.Robolectric;
5157
import org.robolectric.RobolectricTestRunner;
5258
import org.robolectric.android.controller.ActivityController;
@@ -629,4 +635,35 @@ public void performsDefaultBehaviorWhenNoDelegateProvided() {
629635

630636
verify(mockActivity, times(1)).finish();
631637
}
638+
639+
@Test
640+
public void startChoosenActivityWhenSharingText() {
641+
Activity mockActivity = mock(Activity.class);
642+
PlatformChannel mockPlatformChannel = mock(PlatformChannel.class);
643+
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
644+
PlatformPlugin platformPlugin =
645+
new PlatformPlugin(mockActivity, mockPlatformChannel, mockPlatformPluginDelegate);
646+
647+
// Mock Intent.createChooser (in real application it opens a chooser where the user can
648+
// select which application will be used to share the selected text).
649+
Intent choosenIntent = new Intent();
650+
MockedStatic<Intent> intentClass = mockStatic(Intent.class);
651+
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
652+
intentClass
653+
.when(() -> Intent.createChooser(intentCaptor.capture(), any()))
654+
.thenReturn(choosenIntent);
655+
656+
final String expectedContent = "Flutter";
657+
platformPlugin.mPlatformMessageHandler.share(expectedContent);
658+
659+
// Activity.startActivity should have been called.
660+
verify(mockActivity, times(1)).startActivity(choosenIntent);
661+
662+
// The intent action created by the plugin and passed to Intent.createChooser should be
663+
// 'Intent.ACTION_SEND'.
664+
Intent sendToIntent = intentCaptor.getValue();
665+
assertEquals(sendToIntent.getAction(), Intent.ACTION_SEND);
666+
assertEquals(sendToIntent.getType(), "text/plain");
667+
assertEquals(sendToIntent.getStringExtra(Intent.EXTRA_TEXT), expectedContent);
668+
}
632669
}

0 commit comments

Comments
 (0)