Skip to content

Commit 74fd094

Browse files
authored
[image_picker_android] Adjust file extension in FileUtils when it does not match its mime type (flutter#3409)
[image_picker_android] Adjust file extension in FileUtils when it does not match its mime type
1 parent d0de136 commit 74fd094

File tree

4 files changed

+57
-10
lines changed

4 files changed

+57
-10
lines changed

packages/image_picker/image_picker_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.8.6+5
2+
3+
* Fixes case when file extension returned from the OS does not match its real mime type.
4+
15
## 0.8.6+4
26

37
* Bumps androidx.exifinterface:exifinterface from 1.3.3 to 1.3.6.

packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ class FileUtils {
4545
* <p>Each file is placed in its own directory to avoid conflicts according to the following
4646
* scheme: {cacheDir}/{randomUuid}/{fileName}
4747
*
48+
* <p>File extension is changed to match MIME type of the file, if known. Otherwise, the extension
49+
* is left unchanged.
50+
*
4851
* <p>If the original file name is unknown, a predefined "image_picker" filename is used and the
4952
* file extension is deduced from the mime type (with fallback to ".jpg" in case of failure).
5053
*/
@@ -57,9 +60,14 @@ String getPathFromUri(final Context context, final Uri uri) {
5760
// just clear the picked files after the app startup.
5861
targetDirectory.deleteOnExit();
5962
String fileName = getImageName(context, uri);
63+
String extension = getImageExtension(context, uri);
64+
6065
if (fileName == null) {
6166
Log.w("FileUtils", "Cannot get file name for " + uri);
62-
fileName = "image_picker" + getImageExtension(context, uri);
67+
if (extension == null) extension = ".jpg";
68+
fileName = "image_picker" + extension;
69+
} else if (extension != null) {
70+
fileName = getBaseName(fileName) + extension;
6371
}
6472
File file = new File(targetDirectory, fileName);
6573
try (OutputStream outputStream = new FileOutputStream(file)) {
@@ -74,7 +82,7 @@ String getPathFromUri(final Context context, final Uri uri) {
7482
}
7583
}
7684

77-
/** @return extension of image with dot, or default .jpg if it none. */
85+
/** @return extension of image with dot, or null if it's empty. */
7886
private static String getImageExtension(Context context, Uri uriImage) {
7987
String extension;
8088

@@ -88,12 +96,11 @@ private static String getImageExtension(Context context, Uri uriImage) {
8896
Uri.fromFile(new File(uriImage.getPath())).toString());
8997
}
9098
} catch (Exception e) {
91-
extension = null;
99+
return null;
92100
}
93101

94102
if (extension == null || extension.isEmpty()) {
95-
//default extension for matches the previous behavior of the plugin
96-
extension = "jpg";
103+
return null;
97104
}
98105

99106
return "." + extension;
@@ -121,4 +128,9 @@ private static void copy(InputStream in, OutputStream out) throws IOException {
121128
}
122129
out.flush();
123130
}
131+
132+
private static String getBaseName(String fileName) {
133+
// Basename is everything before the last '.'.
134+
return fileName.substring(0, fileName.lastIndexOf('.'));
135+
}
124136
}

packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.database.MatrixCursor;
1616
import android.net.Uri;
1717
import android.provider.MediaStore;
18+
import android.webkit.MimeTypeMap;
1819
import androidx.annotation.NonNull;
1920
import androidx.annotation.Nullable;
2021
import androidx.test.core.app.ApplicationProvider;
@@ -29,6 +30,7 @@
2930
import org.robolectric.Robolectric;
3031
import org.robolectric.RobolectricTestRunner;
3132
import org.robolectric.shadows.ShadowContentResolver;
33+
import org.robolectric.shadows.ShadowMimeTypeMap;
3234

3335
@RunWith(RobolectricTestRunner.class)
3436
public class FileUtilTest {
@@ -42,6 +44,10 @@ public void before() {
4244
context = ApplicationProvider.getApplicationContext();
4345
shadowContentResolver = shadowOf(context.getContentResolver());
4446
fileUtils = new FileUtils();
47+
ShadowMimeTypeMap mimeTypeMap = shadowOf(MimeTypeMap.getSingleton());
48+
mimeTypeMap.addExtensionMimeTypMapping("jpg", "image/jpeg");
49+
mimeTypeMap.addExtensionMimeTypMapping("png", "image/png");
50+
mimeTypeMap.addExtensionMimeTypMapping("webp", "image/webp");
4551
}
4652

4753
@Test
@@ -74,15 +80,38 @@ public void FileUtil_getImageExtension() throws IOException {
7480

7581
@Test
7682
public void FileUtil_getImageName() throws IOException {
77-
Uri uri = Uri.parse("content://dummy/dummy.png");
83+
Uri uri = MockContentProvider.PNG_URI;
84+
Robolectric.buildContentProvider(MockContentProvider.class).create("dummy");
85+
shadowContentResolver.registerInputStream(
86+
uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8)));
87+
String path = fileUtils.getPathFromUri(context, uri);
88+
assertTrue(path.endsWith("a.b.png"));
89+
}
90+
91+
@Test
92+
public void FileUtil_getImageName_mismatchedType() throws IOException {
93+
Uri uri = MockContentProvider.WEBP_URI;
7894
Robolectric.buildContentProvider(MockContentProvider.class).create("dummy");
7995
shadowContentResolver.registerInputStream(
8096
uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8)));
8197
String path = fileUtils.getPathFromUri(context, uri);
82-
assertTrue(path.endsWith("dummy.png"));
98+
assertTrue(path.endsWith("c.d.webp"));
99+
}
100+
101+
@Test
102+
public void FileUtil_getImageName_unknownType() throws IOException {
103+
Uri uri = MockContentProvider.UNKNOWN_URI;
104+
Robolectric.buildContentProvider(MockContentProvider.class).create("dummy");
105+
shadowContentResolver.registerInputStream(
106+
uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8)));
107+
String path = fileUtils.getPathFromUri(context, uri);
108+
assertTrue(path.endsWith("e.f.g"));
83109
}
84110

85111
private static class MockContentProvider extends ContentProvider {
112+
public static final Uri PNG_URI = Uri.parse("content://dummy/a.b.png");
113+
public static final Uri WEBP_URI = Uri.parse("content://dummy/c.d.png");
114+
public static final Uri UNKNOWN_URI = Uri.parse("content://dummy/e.f.g");
86115

87116
@Override
88117
public boolean onCreate() {
@@ -98,14 +127,16 @@ public Cursor query(
98127
@Nullable String[] selectionArgs,
99128
@Nullable String sortOrder) {
100129
MatrixCursor cursor = new MatrixCursor(new String[] {MediaStore.MediaColumns.DISPLAY_NAME});
101-
cursor.addRow(new Object[] {"dummy.png"});
130+
cursor.addRow(new Object[] {uri.getLastPathSegment()});
102131
return cursor;
103132
}
104133

105134
@Nullable
106135
@Override
107136
public String getType(@NonNull Uri uri) {
108-
return "image/png";
137+
if (uri.equals(PNG_URI)) return "image/png";
138+
if (uri.equals(WEBP_URI)) return "image/webp";
139+
return null;
109140
}
110141

111142
@Nullable

packages/image_picker/image_picker_android/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ description: Android implementation of the image_picker plugin.
33
repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
55

6-
version: 0.8.6+4
6+
version: 0.8.6+5
77

88
environment:
99
sdk: ">=2.17.0 <3.0.0"

0 commit comments

Comments
 (0)