diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 9591cf3adb65..358e20c78c34 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.6+5 + +* Fixes case when file extension returned from the OS does not match its real mime type. + ## 0.8.6+4 * Bumps androidx.exifinterface:exifinterface from 1.3.3 to 1.3.6. diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java index 449480c19d9c..4ca970a3344a 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java @@ -45,6 +45,9 @@ class FileUtils { *

Each file is placed in its own directory to avoid conflicts according to the following * scheme: {cacheDir}/{randomUuid}/{fileName} * + *

File extension is changed to match MIME type of the file, if known. Otherwise, the extension + * is left unchanged. + * *

If the original file name is unknown, a predefined "image_picker" filename is used and the * file extension is deduced from the mime type (with fallback to ".jpg" in case of failure). */ @@ -57,9 +60,14 @@ String getPathFromUri(final Context context, final Uri uri) { // just clear the picked files after the app startup. targetDirectory.deleteOnExit(); String fileName = getImageName(context, uri); + String extension = getImageExtension(context, uri); + if (fileName == null) { Log.w("FileUtils", "Cannot get file name for " + uri); - fileName = "image_picker" + getImageExtension(context, uri); + if (extension == null) extension = ".jpg"; + fileName = "image_picker" + extension; + } else if (extension != null) { + fileName = getBaseName(fileName) + extension; } File file = new File(targetDirectory, fileName); try (OutputStream outputStream = new FileOutputStream(file)) { @@ -74,7 +82,7 @@ String getPathFromUri(final Context context, final Uri uri) { } } - /** @return extension of image with dot, or default .jpg if it none. */ + /** @return extension of image with dot, or null if it's empty. */ private static String getImageExtension(Context context, Uri uriImage) { String extension; @@ -88,12 +96,11 @@ private static String getImageExtension(Context context, Uri uriImage) { Uri.fromFile(new File(uriImage.getPath())).toString()); } } catch (Exception e) { - extension = null; + return null; } if (extension == null || extension.isEmpty()) { - //default extension for matches the previous behavior of the plugin - extension = "jpg"; + return null; } return "." + extension; @@ -121,4 +128,9 @@ private static void copy(InputStream in, OutputStream out) throws IOException { } out.flush(); } + + private static String getBaseName(String fileName) { + // Basename is everything before the last '.'. + return fileName.substring(0, fileName.lastIndexOf('.')); + } } diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java index 0ea0173fa954..d125b78150cc 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java @@ -15,6 +15,7 @@ import android.database.MatrixCursor; import android.net.Uri; import android.provider.MediaStore; +import android.webkit.MimeTypeMap; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; @@ -29,6 +30,7 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.shadows.ShadowContentResolver; +import org.robolectric.shadows.ShadowMimeTypeMap; @RunWith(RobolectricTestRunner.class) public class FileUtilTest { @@ -42,6 +44,10 @@ public void before() { context = ApplicationProvider.getApplicationContext(); shadowContentResolver = shadowOf(context.getContentResolver()); fileUtils = new FileUtils(); + ShadowMimeTypeMap mimeTypeMap = shadowOf(MimeTypeMap.getSingleton()); + mimeTypeMap.addExtensionMimeTypMapping("jpg", "image/jpeg"); + mimeTypeMap.addExtensionMimeTypMapping("png", "image/png"); + mimeTypeMap.addExtensionMimeTypMapping("webp", "image/webp"); } @Test @@ -74,15 +80,38 @@ public void FileUtil_getImageExtension() throws IOException { @Test public void FileUtil_getImageName() throws IOException { - Uri uri = Uri.parse("content://dummy/dummy.png"); + Uri uri = MockContentProvider.PNG_URI; + Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); + shadowContentResolver.registerInputStream( + uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); + String path = fileUtils.getPathFromUri(context, uri); + assertTrue(path.endsWith("a.b.png")); + } + + @Test + public void FileUtil_getImageName_mismatchedType() throws IOException { + Uri uri = MockContentProvider.WEBP_URI; Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); shadowContentResolver.registerInputStream( uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); String path = fileUtils.getPathFromUri(context, uri); - assertTrue(path.endsWith("dummy.png")); + assertTrue(path.endsWith("c.d.webp")); + } + + @Test + public void FileUtil_getImageName_unknownType() throws IOException { + Uri uri = MockContentProvider.UNKNOWN_URI; + Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); + shadowContentResolver.registerInputStream( + uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); + String path = fileUtils.getPathFromUri(context, uri); + assertTrue(path.endsWith("e.f.g")); } private static class MockContentProvider extends ContentProvider { + public static final Uri PNG_URI = Uri.parse("content://dummy/a.b.png"); + public static final Uri WEBP_URI = Uri.parse("content://dummy/c.d.png"); + public static final Uri UNKNOWN_URI = Uri.parse("content://dummy/e.f.g"); @Override public boolean onCreate() { @@ -98,14 +127,16 @@ public Cursor query( @Nullable String[] selectionArgs, @Nullable String sortOrder) { MatrixCursor cursor = new MatrixCursor(new String[] {MediaStore.MediaColumns.DISPLAY_NAME}); - cursor.addRow(new Object[] {"dummy.png"}); + cursor.addRow(new Object[] {uri.getLastPathSegment()}); return cursor; } @Nullable @Override public String getType(@NonNull Uri uri) { - return "image/png"; + if (uri.equals(PNG_URI)) return "image/png"; + if (uri.equals(WEBP_URI)) return "image/webp"; + return null; } @Nullable diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index 5c9be4f61107..41c8277e3df9 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -3,7 +3,7 @@ description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.6+4 +version: 0.8.6+5 environment: sdk: ">=2.17.0 <3.0.0"