diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java index 41ffa23853c7c..c3c14dff25213 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java @@ -11,6 +11,7 @@ import android.content.ClipDescription; import android.content.ClipboardManager; import android.content.Context; +import android.content.res.AssetFileDescriptor; import android.os.Build; import android.view.HapticFeedbackConstants; import android.view.SoundEffectConstants; @@ -25,6 +26,7 @@ import io.flutter.Log; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.List; /** Android implementation of the platform plugin. */ @@ -512,14 +514,21 @@ private CharSequence getClipboardData(PlatformChannel.ClipboardContentFormat for if (!clipboard.hasPrimaryClip()) return null; + CharSequence charSequence = null; try { ClipData clip = clipboard.getPrimaryClip(); if (clip == null) return null; if (format == null || format == PlatformChannel.ClipboardContentFormat.PLAIN_TEXT) { ClipData.Item item = clip.getItemAt(0); + AssetFileDescriptor assetFileDescriptor = null; if (item.getUri() != null) - activity.getContentResolver().openTypedAssetFileDescriptor(item.getUri(), "text/*", null); - return item.coerceToText(activity); + assetFileDescriptor = + activity + .getContentResolver() + .openTypedAssetFileDescriptor(item.getUri(), "text/*", null); + charSequence = item.coerceToText(activity); + if (assetFileDescriptor != null) assetFileDescriptor.close(); + return charSequence; } } catch (SecurityException e) { Log.w( @@ -531,6 +540,9 @@ private CharSequence getClipboardData(PlatformChannel.ClipboardContentFormat for return null; } catch (FileNotFoundException e) { return null; + } catch (IOException e) { + Log.w(TAG, "Failed to close AssetFileDescriptor while accessing clipboard data.", e); + return charSequence; } return 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 23b7fbf5b6a98..51e66c91db30b 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java @@ -11,6 +11,10 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; 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.never; import static org.mockito.Mockito.spy; @@ -20,9 +24,11 @@ import android.app.Activity; import android.content.ClipData; +import android.content.ClipDescription; import android.content.ClipboardManager; import android.content.ContentResolver; import android.content.Context; +import android.content.res.AssetFileDescriptor; import android.net.Uri; import android.os.Build; import android.view.View; @@ -84,18 +90,39 @@ public void platformPlugin_getClipboardData() throws IOException { PlatformChannel fakePlatformChannel = mock(PlatformChannel.class); PlatformPlugin platformPlugin = new PlatformPlugin(fakeActivity, fakePlatformChannel); + // Successfully get the contents of the primary clip when they contain text. ClipboardContentFormat clipboardFormat = ClipboardContentFormat.PLAIN_TEXT; assertNull(platformPlugin.mPlatformMessageHandler.getClipboardData(clipboardFormat)); ClipData clip = ClipData.newPlainText("label", "Text"); clipboardManager.setPrimaryClip(clip); assertNotNull(platformPlugin.mPlatformMessageHandler.getClipboardData(clipboardFormat)); - ContentResolver contentResolver = ctx.getContentResolver(); + // Return null when the primary clip contains non-text media. + ContentResolver contentResolver = spy(ctx.getContentResolver()); when(fakeActivity.getContentResolver()).thenReturn(contentResolver); Uri uri = Uri.parse("content://media/external_primary/images/media/"); clip = ClipData.newUri(contentResolver, "URI", uri); clipboardManager.setPrimaryClip(clip); assertNull(platformPlugin.mPlatformMessageHandler.getClipboardData(clipboardFormat)); + + // Still return text when the AssetFileDescriptor throws an IOException. + when(fakeActivity.getContentResolver()).thenReturn(contentResolver); + ClipDescription clipDescription = + new ClipDescription( + "label", + new String[] { + ClipDescription.MIMETYPE_TEXT_PLAIN, ClipDescription.MIMETYPE_TEXT_URILIST + }); + ClipData.Item clipDataItem = new ClipData.Item("Text", null, uri); + ClipData clipData = new ClipData(clipDescription, clipDataItem); + clipboardManager.setPrimaryClip(clipData); + AssetFileDescriptor fakeAssetFileDescriptor = mock(AssetFileDescriptor.class); + doReturn(fakeAssetFileDescriptor) + .when(contentResolver) + .openTypedAssetFileDescriptor(eq(uri), anyString(), eq(null)); + doThrow(new IOException()).when(fakeAssetFileDescriptor).close(); + assertNotNull(platformPlugin.mPlatformMessageHandler.getClipboardData(clipboardFormat)); + verify(fakeAssetFileDescriptor).close(); } @SuppressWarnings("deprecation") diff --git a/tools/android_lint/baseline.xml b/tools/android_lint/baseline.xml index b540917bd520e..2fccb83d76cf3 100644 --- a/tools/android_lint/baseline.xml +++ b/tools/android_lint/baseline.xml @@ -67,17 +67,6 @@ column="82"/> - - - -