[image_picker]fix a crash when a non-image file is picked. (#2293)
diff --git a/packages/image_picker/CHANGELOG.md b/packages/image_picker/CHANGELOG.md
index 11c48e8..96bb4fc 100644
--- a/packages/image_picker/CHANGELOG.md
+++ b/packages/image_picker/CHANGELOG.md
@@ -1,6 +1,11 @@
+## 0.6.2+1
+
+* Android: Fix a crash when a non-image file is picked.
+* Android: Fix unwanted bitmap scaling.
+
## 0.6.2
-* iOS: Fixes an issue where picking conent from Gallery would result in a crash on iOS 13.
+* iOS: Fixes an issue where picking content from Gallery would result in a crash on iOS 13.
## 0.6.1+11
diff --git a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
index f9318e9..e34a3b5 100644
--- a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
+++ b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
@@ -521,10 +521,7 @@
if (methodCall != null) {
Double maxWidth = methodCall.argument("maxWidth");
Double maxHeight = methodCall.argument("maxHeight");
- int imageQuality =
- methodCall.argument("imageQuality") == null
- ? 100
- : (int) methodCall.argument("imageQuality");
+ Integer imageQuality = methodCall.argument("imageQuality");
String finalImagePath =
imageResizer.resizeImageIfNeeded(path, maxWidth, maxHeight, imageQuality);
diff --git a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java
index ab3120a..ca0498b 100644
--- a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java
+++ b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java
@@ -7,6 +7,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
+import androidx.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -28,31 +29,39 @@
* <p>If no resizing is needed, returns the path for the original image.
*/
String resizeImageIfNeeded(
- String imagePath, Double maxWidth, Double maxHeight, int imageQuality) {
+ String imagePath,
+ @Nullable Double maxWidth,
+ @Nullable Double maxHeight,
+ @Nullable Integer imageQuality) {
boolean shouldScale =
- maxWidth != null || maxHeight != null || (imageQuality > -1 && imageQuality < 101);
-
- if (!shouldScale) {
- return imagePath;
+ maxWidth != null || maxHeight != null || isImageQualityValid(imageQuality);
+ String[] pathParts = imagePath.split("/");
+ String imageName = pathParts[pathParts.length - 1];
+ File file;
+ Bitmap bmp = decodeFile(imagePath);
+ if (bmp == null) {
+ return null;
}
-
try {
- File scaledImage = resizedImage(imagePath, maxWidth, maxHeight, imageQuality);
- exifDataCopier.copyExif(imagePath, scaledImage.getPath());
-
- return scaledImage.getPath();
+ if (!shouldScale) {
+ file = createImageOnExternalDirectory(imageName, bmp, 100);
+ } else {
+ file = resizedImage(bmp, maxWidth, maxHeight, imageQuality, imageName);
+ }
+ copyExif(imagePath, file.getPath());
+ return file.getPath();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
- private File resizedImage(String path, Double maxWidth, Double maxHeight, int imageQuality)
+ private File resizedImage(
+ Bitmap bmp, Double maxWidth, Double maxHeight, Integer imageQuality, String outputImageName)
throws IOException {
- Bitmap bmp = BitmapFactory.decodeFile(path);
double originalWidth = bmp.getWidth() * 1.0;
double originalHeight = bmp.getHeight() * 1.0;
- if (imageQuality < 0 || imageQuality > 100) {
+ if (!isImageQualityValid(imageQuality)) {
imageQuality = 100;
}
@@ -91,24 +100,51 @@
}
}
- Bitmap scaledBmp = Bitmap.createScaledBitmap(bmp, width.intValue(), height.intValue(), false);
+ Bitmap scaledBmp = createScaledBitmap(bmp, width.intValue(), height.intValue(), false);
+ File file =
+ createImageOnExternalDirectory("/scaled_" + outputImageName, scaledBmp, imageQuality);
+ return file;
+ }
+
+ private File createFile(File externalFilesDirectory, String child) {
+ return new File(externalFilesDirectory, child);
+ }
+
+ private FileOutputStream createOutputStream(File imageFile) throws IOException {
+ return new FileOutputStream(imageFile);
+ }
+
+ private void copyExif(String filePathOri, String filePathDest) {
+ exifDataCopier.copyExif(filePathOri, filePathDest);
+ }
+
+ private Bitmap decodeFile(String path) {
+ return BitmapFactory.decodeFile(path);
+ }
+
+ private Bitmap createScaledBitmap(Bitmap bmp, int width, int height, boolean filter) {
+ return Bitmap.createScaledBitmap(bmp, width, height, filter);
+ }
+
+ private boolean isImageQualityValid(Integer imageQuality) {
+ return imageQuality != null && imageQuality > 0 && imageQuality < 100;
+ }
+
+ private File createImageOnExternalDirectory(String name, Bitmap bitmap, int imageQuality)
+ throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- boolean saveAsPNG = bmp.hasAlpha();
+ boolean saveAsPNG = bitmap.hasAlpha();
if (saveAsPNG) {
Log.d(
"ImageResizer",
"image_picker: compressing is not supported for type PNG. Returning the image with original quality");
}
- scaledBmp.compress(
+ bitmap.compress(
saveAsPNG ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG,
imageQuality,
outputStream);
-
- String[] pathParts = path.split("/");
- String imageName = pathParts[pathParts.length - 1];
-
- File imageFile = new File(externalFilesDirectory, "/scaled_" + imageName);
- FileOutputStream fileOutput = new FileOutputStream(imageFile);
+ File imageFile = createFile(externalFilesDirectory, name);
+ FileOutputStream fileOutput = createOutputStream(imageFile);
fileOutput.write(outputStream.toByteArray());
fileOutput.close();
return imageFile;
diff --git a/packages/image_picker/example/android/app/build.gradle b/packages/image_picker/example/android/app/build.gradle
index 800e3e8..483268f 100755
--- a/packages/image_picker/example/android/app/build.gradle
+++ b/packages/image_picker/example/android/app/build.gradle
@@ -62,4 +62,5 @@
testImplementation 'org.mockito:mockito-core:2.17.0'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+ testImplementation "org.robolectric:robolectric:3.3.2"
}
diff --git a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java
index 8e89a15..51733a5 100644
--- a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java
+++ b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java
@@ -25,10 +25,7 @@
import org.mockito.MockitoAnnotations;
public class ImagePickerCacheTest {
- private static final double WIDTH = 10.0;
- private static final double HEIGHT = 10.0;
private static final int IMAGE_QUALITY = 90;
- private static final String PATH = "a_mock_path";
@Mock Activity mockActivity;
@Mock SharedPreferences mockPreference;
diff --git a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
index 60e1167..88fa337 100644
--- a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
+++ b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
@@ -24,9 +24,9 @@
import org.mockito.MockitoAnnotations;
public class ImagePickerDelegateTest {
- private static final double WIDTH = 10.0;
- private static final double HEIGHT = 10.0;
- private static final int IMAGE_QUALITY = 100;
+ private static final Double WIDTH = 10.0;
+ private static final Double HEIGHT = 10.0;
+ private static final Integer IMAGE_QUALITY = 90;
@Mock Activity mockActivity;
@Mock ImageResizer mockImageResizer;
@@ -62,13 +62,15 @@
when(mockFileUtils.getPathFromUri(any(Context.class), any(Uri.class)))
.thenReturn("pathFromUri");
+ when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, null, null))
+ .thenReturn("originalPath");
when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, null, IMAGE_QUALITY))
.thenReturn("originalPath");
- when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, HEIGHT, IMAGE_QUALITY))
+ when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, HEIGHT, null))
.thenReturn("scaledPath");
- when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, null, IMAGE_QUALITY))
+ when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, null, null))
.thenReturn("scaledPath");
- when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, HEIGHT, IMAGE_QUALITY))
+ when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, HEIGHT, null))
.thenReturn("scaledPath");
mockFileUriResolver = new MockFileUriResolver();
diff --git a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java
new file mode 100644
index 0000000..aac7472
--- /dev/null
+++ b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java
@@ -0,0 +1,65 @@
+// Copyright 2019 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.imagepicker;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import java.io.File;
+import java.io.IOException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+// RobolectricTestRunner always creates a default mock bitmap when reading from file. So we cannot actually test the scaling.
+// But we can still test whether the original or scaled file is created.
+@RunWith(RobolectricTestRunner.class)
+public class ImageResizerTest {
+
+ ImageResizer resizer;
+ File imageFile;
+ File externalDirectory;
+ Bitmap originalImageBitmap;
+
+ @Before
+ public void setUp() throws IOException {
+ MockitoAnnotations.initMocks(this);
+ imageFile = new File(getClass().getClassLoader().getResource("pngImage.png").getFile());
+ originalImageBitmap = BitmapFactory.decodeFile(imageFile.getPath());
+ TemporaryFolder temporaryFolder = new TemporaryFolder();
+ temporaryFolder.create();
+ externalDirectory = temporaryFolder.newFolder("image_picker_testing_path");
+ resizer = new ImageResizer(externalDirectory, new ExifDataCopier());
+ }
+
+ @Test
+ public void onResizeImageIfNeeded_WhenQualityIsNull_ShoultNotResize_ReturnTheUnscaledFile() {
+ String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, null);
+ assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/pngImage.png"));
+ }
+
+ @Test
+ public void onResizeImageIfNeeded_WhenQualityIsNotNull_ShoulResize_ReturnResizedFile() {
+ String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, 50);
+ assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png"));
+ }
+
+ @Test
+ public void onResizeImageIfNeeded_WhenWidthIsNotNull_ShoulResize_ReturnResizedFile() {
+ String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, null, null);
+ assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png"));
+ }
+
+ @Test
+ public void onResizeImageIfNeeded_WhenHeightIsNotNull_ShoulResize_ReturnResizedFile() {
+ String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, 50.0, null);
+ assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png"));
+ }
+}
diff --git a/packages/image_picker/example/android/app/src/test/resources/pngImage.png b/packages/image_picker/example/android/app/src/test/resources/pngImage.png
new file mode 100644
index 0000000..22ac5a5
--- /dev/null
+++ b/packages/image_picker/example/android/app/src/test/resources/pngImage.png
Binary files differ
diff --git a/packages/image_picker/pubspec.yaml b/packages/image_picker/pubspec.yaml
index bca5a9d..0e47c53 100755
--- a/packages/image_picker/pubspec.yaml
+++ b/packages/image_picker/pubspec.yaml
@@ -5,7 +5,7 @@
- Flutter Team <flutter-dev@googlegroups.com>
- Rhodes Davis Jr. <rody.davis.jr@gmail.com>
homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker
-version: 0.6.2
+version: 0.6.2+1
flutter:
plugin: