Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Add support for the PlatformChannel "Share.invoke" command #48265

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -549,6 +554,13 @@ default void setFrameworkHandlesBack(boolean frameworkHandlesBack) {}
* can be pasted.
*/
boolean clipboardHasStrings();

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good documentation!

* 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. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.os.Build;
import android.view.HapticFeedbackConstants;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -42,4 +45,26 @@ 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<String> valueCapture = ArgumentCaptor.forClass(String.class);
doNothing().when(mockMessageHandler).share(valueCapture.capture());

final String expectedContent = "Flutter";
MethodCall methodCall = new MethodCall("Share.invoke", expectedContent);
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult);

assertEquals(valueCapture.getValue(), expectedContent);
verify(mockResult).success(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,6 +31,7 @@
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import android.os.Build;
Expand All @@ -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;
Expand Down Expand Up @@ -629,4 +635,35 @@ 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<Intent> intentClass = mockStatic(Intent.class);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
intentClass
.when(() -> Intent.createChooser(intentCaptor.capture(), any()))
.thenReturn(choosenIntent);

final String expectedContent = "Flutter";
platformPlugin.mPlatformMessageHandler.share(expectedContent);

// 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), expectedContent);
}
}