diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java index cc3cb2a7ff0b811d9cf1ff66e0e93a714e7ff768..13dacbee2c9efcf6c3ff8b9fdeef9c266c03fde9 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java @@ -7,6 +7,7 @@ package io.flutter.plugin.platform; import android.app.Activity; import android.app.ActivityManager.TaskDescription; import android.content.ClipData; +import android.content.ClipDescription; import android.content.ClipboardManager; import android.content.Context; import android.os.Build; @@ -124,10 +125,7 @@ public class PlatformPlugin { @Override public boolean clipboardHasStrings() { - CharSequence data = - PlatformPlugin.this.getClipboardData( - PlatformChannel.ClipboardContentFormat.PLAIN_TEXT); - return data != null && data.length() > 0; + return PlatformPlugin.this.clipboardHasStrings(); } }; @@ -490,4 +488,20 @@ public class PlatformPlugin { ClipData clip = ClipData.newPlainText("text label?", text); clipboard.setPrimaryClip(clip); } + + private boolean clipboardHasStrings() { + ClipboardManager clipboard = + (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + // Android 12 introduces a toast message that appears when an app reads the clipboard. To avoid + // unintended access, call the appropriate APIs to receive information about the current content + // that's on the clipboard (rather than the actual content itself). + if (!clipboard.hasPrimaryClip()) { + return false; + } + ClipDescription description = clipboard.getPrimaryClipDescription(); + if (description == null) { + return false; + } + return description.hasMimeType("text/*"); + } } 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 bdb1b11e6ddf5077c021a4f5d7d8a859a6ceac76..e67769cef904ee22e630625d8f4fbd17a342c1a0 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java @@ -7,6 +7,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -96,10 +97,11 @@ public class PlatformPluginTest { assertEquals(dataInputStream.read(), uriInputStream.read()); } + @Config(sdk = 28) @Test public void platformPlugin_hasStrings() { ClipboardManager clipboardManager = - RuntimeEnvironment.application.getSystemService(ClipboardManager.class); + spy(RuntimeEnvironment.application.getSystemService(ClipboardManager.class)); View fakeDecorView = mock(View.class); Window fakeWindow = mock(Window.class); @@ -110,13 +112,44 @@ public class PlatformPluginTest { PlatformChannel fakePlatformChannel = mock(PlatformChannel.class); PlatformPlugin platformPlugin = new PlatformPlugin(fakeActivity, fakePlatformChannel); + // Plain text ClipData clip = ClipData.newPlainText("label", "Text"); clipboardManager.setPrimaryClip(clip); assertTrue(platformPlugin.mPlatformMessageHandler.clipboardHasStrings()); + // Empty plain text clip = ClipData.newPlainText("", ""); clipboardManager.setPrimaryClip(clip); + // Without actually accessing clipboard data (preferred behavior), it is not possible to + // distinguish between empty and non-empty string contents. + assertTrue(platformPlugin.mPlatformMessageHandler.clipboardHasStrings()); + + // HTML text + clip = ClipData.newHtmlText("motto", "Don't be evil", "Don't be evil"); + clipboardManager.setPrimaryClip(clip); + assertTrue(platformPlugin.mPlatformMessageHandler.clipboardHasStrings()); + + // Text MIME type + clip = new ClipData("label", new String[] {"text/something"}, new ClipData.Item("content")); + clipboardManager.setPrimaryClip(clip); + assertTrue(platformPlugin.mPlatformMessageHandler.clipboardHasStrings()); + + // Other MIME type + clip = + new ClipData( + "label", new String[] {"application/octet-stream"}, new ClipData.Item("content")); + clipboardManager.setPrimaryClip(clip); assertFalse(platformPlugin.mPlatformMessageHandler.clipboardHasStrings()); + + if (Build.VERSION.SDK_INT >= 28) { + // Empty clipboard + clipboardManager.clearPrimaryClip(); + assertFalse(platformPlugin.mPlatformMessageHandler.clipboardHasStrings()); + } + + // Verify that the clipboard contents are never accessed. + verify(clipboardManager, never()).getPrimaryClip(); + verify(clipboardManager, never()).getText(); } @Config(sdk = 29)