Skip to content

Commit 33015c6

Browse files
authored
hasStrings on Android (flutter#20393)
hasStrings message for checking for pasteable clipboard contents without actually reading them, for iOS14 clipboard alerts.
1 parent 0d83051 commit 33015c6

File tree

6 files changed

+99
-2
lines changed

6 files changed

+99
-2
lines changed

shell/platform/android/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ action("robolectric_tests") {
435435
"test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java",
436436
"test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java",
437437
"test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java",
438+
"test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java",
438439
"test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java",
439440
"test/io/flutter/external/FlutterLaunchTests.java",
440441
"test/io/flutter/plugin/common/StandardMessageCodecTest.java",

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class PlatformChannel {
3030
@Nullable private PlatformMessageHandler platformMessageHandler;
3131

3232
@NonNull @VisibleForTesting
33-
protected final MethodChannel.MethodCallHandler parsingMethodCallHandler =
33+
final MethodChannel.MethodCallHandler parsingMethodCallHandler =
3434
new MethodChannel.MethodCallHandler() {
3535
@Override
3636
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
@@ -155,6 +155,14 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result
155155
result.success(null);
156156
break;
157157
}
158+
case "Clipboard.hasStrings":
159+
{
160+
boolean hasStrings = platformMessageHandler.clipboardHasStrings();
161+
JSONObject response = new JSONObject();
162+
response.put("value", hasStrings);
163+
result.success(response);
164+
break;
165+
}
158166
default:
159167
result.notImplemented();
160168
break;
@@ -426,6 +434,12 @@ public interface PlatformMessageHandler {
426434
* {@code text}.
427435
*/
428436
void setClipboardData(@NonNull String text);
437+
438+
/**
439+
* The Flutter application would like to know if the clipboard currently contains a string that
440+
* can be pasted.
441+
*/
442+
boolean clipboardHasStrings();
429443
}
430444

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

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ public class PlatformPlugin {
3030
private PlatformChannel.SystemChromeStyle currentTheme;
3131
private int mEnabledOverlays;
3232

33-
private final PlatformChannel.PlatformMessageHandler mPlatformMessageHandler =
33+
@VisibleForTesting
34+
final PlatformChannel.PlatformMessageHandler mPlatformMessageHandler =
3435
new PlatformChannel.PlatformMessageHandler() {
3536
@Override
3637
public void playSystemSound(@NonNull PlatformChannel.SoundType soundType) {
@@ -85,6 +86,14 @@ public CharSequence getClipboardData(
8586
public void setClipboardData(@NonNull String text) {
8687
PlatformPlugin.this.setClipboardData(text);
8788
}
89+
90+
@Override
91+
public boolean clipboardHasStrings() {
92+
CharSequence data =
93+
PlatformPlugin.this.getClipboardData(
94+
PlatformChannel.ClipboardContentFormat.PLAIN_TEXT);
95+
return data != null && data.length() > 0;
96+
}
8897
};
8998

9099
public PlatformPlugin(Activity activity, PlatformChannel platformChannel) {

shell/platform/android/test/io/flutter/FlutterTestSuite.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import io.flutter.embedding.engine.RenderingComponentTest;
1818
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistryTest;
1919
import io.flutter.embedding.engine.renderer.FlutterRendererTest;
20+
import io.flutter.embedding.engine.systemchannels.PlatformChannelTest;
2021
import io.flutter.embedding.engine.systemchannels.RestorationChannelTest;
2122
import io.flutter.external.FlutterLaunchTests;
2223
import io.flutter.plugin.common.StandardMessageCodecTest;
@@ -68,6 +69,7 @@
6869
TextInputPluginTest.class,
6970
MouseCursorPluginTest.class,
7071
AccessibilityBridgeTest.class,
72+
PlatformChannelTest.class,
7173
RestorationChannelTest.class,
7274
})
7375
/** Runs all of the unit tests listed in the {@code @SuiteClasses} annotation. */
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.flutter.embedding.engine.systemchannels;
2+
3+
import static org.mockito.Mockito.mock;
4+
import static org.mockito.Mockito.verify;
5+
import static org.mockito.Mockito.when;
6+
7+
import android.content.res.AssetManager;
8+
import io.flutter.embedding.engine.FlutterJNI;
9+
import io.flutter.embedding.engine.dart.DartExecutor;
10+
import io.flutter.plugin.common.MethodCall;
11+
import io.flutter.plugin.common.MethodChannel;
12+
import org.json.JSONException;
13+
import org.json.JSONObject;
14+
import org.junit.Test;
15+
import org.junit.runner.RunWith;
16+
import org.mockito.Matchers;
17+
import org.robolectric.RobolectricTestRunner;
18+
import org.robolectric.annotation.Config;
19+
20+
@Config(manifest = Config.NONE)
21+
@RunWith(RobolectricTestRunner.class)
22+
public class PlatformChannelTest {
23+
@Test
24+
public void platformChannel_hasStringsMessage() {
25+
MethodChannel rawChannel = mock(MethodChannel.class);
26+
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
27+
DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class));
28+
PlatformChannel fakePlatformChannel = new PlatformChannel(dartExecutor);
29+
PlatformChannel.PlatformMessageHandler mockMessageHandler =
30+
mock(PlatformChannel.PlatformMessageHandler.class);
31+
fakePlatformChannel.setPlatformMessageHandler(mockMessageHandler);
32+
Boolean returnValue = true;
33+
when(mockMessageHandler.clipboardHasStrings()).thenReturn(returnValue);
34+
MethodCall methodCall = new MethodCall("Clipboard.hasStrings", null);
35+
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
36+
fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult);
37+
38+
JSONObject expected = new JSONObject();
39+
try {
40+
expected.put("value", returnValue);
41+
} catch (JSONException e) {
42+
}
43+
verify(mockResult).success(Matchers.refEq(expected));
44+
}
45+
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
package io.flutter.plugin.platform;
22

3+
import static org.junit.Assert.assertFalse;
4+
import static org.junit.Assert.assertTrue;
35
import static org.mockito.Mockito.mock;
46
import static org.mockito.Mockito.when;
57

68
import android.app.Activity;
9+
import android.content.ClipboardManager;
10+
import android.content.Context;
711
import android.view.View;
812
import android.view.Window;
913
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
1014
import org.junit.Test;
1115
import org.junit.runner.RunWith;
1216
import org.robolectric.RobolectricTestRunner;
17+
import org.robolectric.RuntimeEnvironment;
1318
import org.robolectric.annotation.Config;
1419

1520
@Config(manifest = Config.NONE)
@@ -32,4 +37,25 @@ public void itIgnoresNewHapticEventsOnOldAndroidPlatforms() {
3237
// SELECTION_CLICK haptic response is only available on "LOLLIPOP" (21) and later.
3338
platformPlugin.vibrateHapticFeedback(PlatformChannel.HapticFeedbackType.SELECTION_CLICK);
3439
}
40+
41+
@Test
42+
public void platformPlugin_hasStrings() {
43+
ClipboardManager clipboardManager =
44+
RuntimeEnvironment.application.getSystemService(ClipboardManager.class);
45+
46+
View fakeDecorView = mock(View.class);
47+
Window fakeWindow = mock(Window.class);
48+
when(fakeWindow.getDecorView()).thenReturn(fakeDecorView);
49+
Activity fakeActivity = mock(Activity.class);
50+
when(fakeActivity.getWindow()).thenReturn(fakeWindow);
51+
when(fakeActivity.getSystemService(Context.CLIPBOARD_SERVICE)).thenReturn(clipboardManager);
52+
PlatformChannel fakePlatformChannel = mock(PlatformChannel.class);
53+
PlatformPlugin platformPlugin = new PlatformPlugin(fakeActivity, fakePlatformChannel);
54+
55+
clipboardManager.setText("iamastring");
56+
assertTrue(platformPlugin.mPlatformMessageHandler.clipboardHasStrings());
57+
58+
clipboardManager.setText("");
59+
assertFalse(platformPlugin.mPlatformMessageHandler.clipboardHasStrings());
60+
}
3561
}

0 commit comments

Comments
 (0)