From ca71bdc5cdff4ee932622d3d8fb8d796ee341c8e Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 8 Aug 2024 13:42:50 +0200 Subject: [PATCH 1/2] fix crash when rounding NaN --- .../mobile_scanner/MobileScanner.kt | 32 ++++++---- .../mobile_scanner/MobileScannerTest.kt | 59 +++++++++++++++++++ 2 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 android/src/test/kotlin/dev/steenbakker/mobile_scanner/MobileScannerTest.kt diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt index 6becac84d..4b2b2376e 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt @@ -170,25 +170,35 @@ class MobileScanner( return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) } - // scales the scanWindow to the provided inputImage and checks if that scaled - // scanWindow contains the barcode - private fun isBarcodeInScanWindow( + // Scales the scanWindow to the provided inputImage and checks if that scaled + // scanWindow contains the barcode. + @VisibleForTesting + fun isBarcodeInScanWindow( scanWindow: List, barcode: Barcode, inputImage: ImageProxy ): Boolean { + // TODO: use `cornerPoints` instead, since the bounding box is not bound to the coordinate system of the input image + // On iOS we do this correctly, so the calculation should match that. val barcodeBoundingBox = barcode.boundingBox ?: return false - val imageWidth = inputImage.height - val imageHeight = inputImage.width + try { + val imageWidth = inputImage.height + val imageHeight = inputImage.width - val left = (scanWindow[0] * imageWidth).roundToInt() - val top = (scanWindow[1] * imageHeight).roundToInt() - val right = (scanWindow[2] * imageWidth).roundToInt() - val bottom = (scanWindow[3] * imageHeight).roundToInt() + val left = (scanWindow[0] * imageWidth).roundToInt() + val top = (scanWindow[1] * imageHeight).roundToInt() + val right = (scanWindow[2] * imageWidth).roundToInt() + val bottom = (scanWindow[3] * imageHeight).roundToInt() - val scaledScanWindow = Rect(left, top, right, bottom) - return scaledScanWindow.contains(barcodeBoundingBox) + val scaledScanWindow = Rect(left, top, right, bottom) + + return scaledScanWindow.contains(barcodeBoundingBox) + } catch (exception: IllegalArgumentException) { + // Rounding of the scan window dimensions can fail, due to encountering NaN. + // If we get NaN, rather than give a false positive, just return false. + return false + } } // Return the best resolution for the actual device orientation. diff --git a/android/src/test/kotlin/dev/steenbakker/mobile_scanner/MobileScannerTest.kt b/android/src/test/kotlin/dev/steenbakker/mobile_scanner/MobileScannerTest.kt new file mode 100644 index 000000000..5947068dd --- /dev/null +++ b/android/src/test/kotlin/dev/steenbakker/mobile_scanner/MobileScannerTest.kt @@ -0,0 +1,59 @@ +package dev.steenbakker.mobile_scanner + +import android.app.Activity +import android.graphics.Rect +import androidx.camera.core.ImageProxy +import com.google.mlkit.vision.barcode.BarcodeScanner +import com.google.mlkit.vision.barcode.BarcodeScannerOptions +import com.google.mlkit.vision.barcode.common.Barcode +import kotlin.test.Test +import org.mockito.Mockito +import io.flutter.view.TextureRegistry +import kotlin.test.expect + +/* + * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. + * + * Once you have built the plugin's example app, you can run these tests from the command + * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or + * you can run them directly from IDEs that support JUnit such as Android Studio. + */ + +internal class MobileScannerTest { + @Test + fun isBarcodeInScanWindow_canHandleNaNValues() { + val barcodeScannerMock = Mockito.mock(BarcodeScanner::class.java) + + val mobileScanner = MobileScanner( + Mockito.mock(Activity::class.java), + Mockito.mock(TextureRegistry::class.java), + { _: List>, _: ByteArray?, _: Int?, _: Int? -> }, + { _: String -> }, + { _: BarcodeScannerOptions? -> barcodeScannerMock } + ) + + // Intentional suppression for the mock value in the test, + // since there is no NaN constant. + @Suppress("DIVISION_BY_ZERO") + val notANumber = 0.0f / 0.0f + + val barcodeMock: Barcode = Mockito.mock(Barcode::class.java) + val imageMock: ImageProxy = Mockito.mock(ImageProxy::class.java) + + // TODO: use corner points instead of bounding box + + // Bounding box that is 100 pixels offset from the left and top, + // and is 100 pixels in width and height. + Mockito.`when`(barcodeMock.boundingBox).thenReturn( + Rect(100, 100, 200, 300)) + Mockito.`when`(imageMock.height).thenReturn(400) + Mockito.`when`(imageMock.width).thenReturn(400) + + // Use a scan window that has an invalid value, but otherwise uses the entire image. + val scanWindow: List = listOf(0f, notANumber, 100f, 100f) + + expect(false) { + mobileScanner.isBarcodeInScanWindow(scanWindow, barcodeMock, imageMock) + } + } +} \ No newline at end of file From 019aa42171a55dc201d1135b8d77d0dad3c48413 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 8 Aug 2024 13:43:48 +0200 Subject: [PATCH 2/2] update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aab8a4ce9..ddc1133fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## NEXT -* Fixed a leak of the barcode scanner on Android. +* [Android] Fixed a leak of the barcode scanner. +* [Android] Fixed a crash when encountering invalid numbers for the scan window. ## 5.1.1 * This release fixes an issue with automatic starts in the examples.