Navaron Bracke

make scanner nullable to prevent leak; use early returns in process image callback; format

@@ -20,12 +20,12 @@ import androidx.camera.core.ImageAnalysis @@ -20,12 +20,12 @@ import androidx.camera.core.ImageAnalysis
20 import androidx.camera.core.ImageProxy 20 import androidx.camera.core.ImageProxy
21 import androidx.camera.core.Preview 21 import androidx.camera.core.Preview
22 import androidx.camera.core.TorchState 22 import androidx.camera.core.TorchState
23 -import androidx.camera.core.resolutionselector.AspectRatioStrategy  
24 import androidx.camera.core.resolutionselector.ResolutionSelector 23 import androidx.camera.core.resolutionselector.ResolutionSelector
25 import androidx.camera.core.resolutionselector.ResolutionStrategy 24 import androidx.camera.core.resolutionselector.ResolutionStrategy
26 import androidx.camera.lifecycle.ProcessCameraProvider 25 import androidx.camera.lifecycle.ProcessCameraProvider
27 import androidx.core.content.ContextCompat 26 import androidx.core.content.ContextCompat
28 import androidx.lifecycle.LifecycleOwner 27 import androidx.lifecycle.LifecycleOwner
  28 +import com.google.mlkit.vision.barcode.BarcodeScanner
29 import com.google.mlkit.vision.barcode.BarcodeScannerOptions 29 import com.google.mlkit.vision.barcode.BarcodeScannerOptions
30 import com.google.mlkit.vision.barcode.BarcodeScanning 30 import com.google.mlkit.vision.barcode.BarcodeScanning
31 import com.google.mlkit.vision.barcode.common.Barcode 31 import com.google.mlkit.vision.barcode.common.Barcode
@@ -37,7 +37,6 @@ import io.flutter.view.TextureRegistry @@ -37,7 +37,6 @@ import io.flutter.view.TextureRegistry
37 import java.io.ByteArrayOutputStream 37 import java.io.ByteArrayOutputStream
38 import kotlin.math.roundToInt 38 import kotlin.math.roundToInt
39 39
40 -  
41 class MobileScanner( 40 class MobileScanner(
42 private val activity: Activity, 41 private val activity: Activity,
43 private val textureRegistry: TextureRegistry, 42 private val textureRegistry: TextureRegistry,
@@ -50,7 +49,7 @@ class MobileScanner( @@ -50,7 +49,7 @@ class MobileScanner(
50 private var camera: Camera? = null 49 private var camera: Camera? = null
51 private var preview: Preview? = null 50 private var preview: Preview? = null
52 private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null 51 private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
53 - private var scanner = BarcodeScanning.getClient() 52 + private var scanner: BarcodeScanner? = null
54 private var lastScanned: List<String?>? = null 53 private var lastScanned: List<String?>? = null
55 private var scannerTimeout = false 54 private var scannerTimeout = false
56 private var displayListener: DisplayManager.DisplayListener? = null 55 private var displayListener: DisplayManager.DisplayListener? = null
@@ -76,76 +75,75 @@ class MobileScanner( @@ -76,76 +75,75 @@ class MobileScanner(
76 scannerTimeout = true 75 scannerTimeout = true
77 } 76 }
78 77
79 - scanner.process(inputImage)  
80 - .addOnSuccessListener { barcodes -> 78 + scanner?.let {
  79 + it.process(inputImage).addOnSuccessListener { barcodes ->
81 if (detectionSpeed == DetectionSpeed.NO_DUPLICATES) { 80 if (detectionSpeed == DetectionSpeed.NO_DUPLICATES) {
82 - val newScannedBarcodes = barcodes.mapNotNull { barcode -> barcode.rawValue }.sorted() 81 + val newScannedBarcodes = barcodes.mapNotNull {
  82 + barcode -> barcode.rawValue
  83 + }.sorted()
  84 +
83 if (newScannedBarcodes == lastScanned) { 85 if (newScannedBarcodes == lastScanned) {
84 // New scanned is duplicate, returning 86 // New scanned is duplicate, returning
85 return@addOnSuccessListener 87 return@addOnSuccessListener
86 } 88 }
87 - if (newScannedBarcodes.isNotEmpty()) lastScanned = newScannedBarcodes 89 + if (newScannedBarcodes.isNotEmpty()) {
  90 + lastScanned = newScannedBarcodes
  91 + }
88 } 92 }
89 93
90 val barcodeMap: MutableList<Map<String, Any?>> = mutableListOf() 94 val barcodeMap: MutableList<Map<String, Any?>> = mutableListOf()
91 95
92 for (barcode in barcodes) { 96 for (barcode in barcodes) {
93 - if (scanWindow != null) {  
94 - val match = isBarcodeInScanWindow(scanWindow!!, barcode, imageProxy)  
95 - if (!match) {  
96 - continue  
97 - } else {  
98 - barcodeMap.add(barcode.data)  
99 - }  
100 - } else { 97 + if (scanWindow == null) {
101 barcodeMap.add(barcode.data) 98 barcodeMap.add(barcode.data)
  99 + continue
102 } 100 }
103 - }  
104 101
  102 + if (isBarcodeInScanWindow(scanWindow!!, barcode, imageProxy)) {
  103 + barcodeMap.add(barcode.data)
  104 + }
  105 + }
105 106
106 - if (barcodeMap.isNotEmpty()) {  
107 - if (returnImage) {  
108 -  
109 - val bitmap = Bitmap.createBitmap(mediaImage.width, mediaImage.height, Bitmap.Config.ARGB_8888)  
110 -  
111 - val imageFormat = YuvToRgbConverter(activity.applicationContext)  
112 -  
113 - imageFormat.yuvToRgb(mediaImage, bitmap) 107 + if (barcodeMap.isEmpty()) {
  108 + return@addOnSuccessListener
  109 + }
114 110
115 - val bmResult = rotateBitmap(bitmap, camera?.cameraInfo?.sensorRotationDegrees?.toFloat() ?: 90f) 111 + if (!returnImage) {
  112 + mobileScannerCallback(
  113 + barcodeMap,
  114 + null,
  115 + null,
  116 + null
  117 + )
  118 + return@addOnSuccessListener
  119 + }
116 120
117 - val stream = ByteArrayOutputStream()  
118 - bmResult.compress(Bitmap.CompressFormat.PNG, 100, stream)  
119 - val byteArray = stream.toByteArray()  
120 - val bmWidth = bmResult.width  
121 - val bmHeight = bmResult.height  
122 - bmResult.recycle() 121 + val bitmap = Bitmap.createBitmap(mediaImage.width, mediaImage.height, Bitmap.Config.ARGB_8888)
  122 + val imageFormat = YuvToRgbConverter(activity.applicationContext)
123 123
  124 + imageFormat.yuvToRgb(mediaImage, bitmap)
124 125
125 - mobileScannerCallback(  
126 - barcodeMap,  
127 - byteArray,  
128 - bmWidth,  
129 - bmHeight  
130 - ) 126 + val bmResult = rotateBitmap(bitmap, camera?.cameraInfo?.sensorRotationDegrees?.toFloat() ?: 90f)
131 127
132 - } else { 128 + val stream = ByteArrayOutputStream()
  129 + bmResult.compress(Bitmap.CompressFormat.PNG, 100, stream)
  130 + val byteArray = stream.toByteArray()
  131 + val bmWidth = bmResult.width
  132 + val bmHeight = bmResult.height
  133 + bmResult.recycle()
133 134
134 - mobileScannerCallback(  
135 - barcodeMap,  
136 - null,  
137 - null,  
138 - null  
139 - )  
140 - }  
141 - }  
142 - }  
143 - .addOnFailureListener { e -> 135 + mobileScannerCallback(
  136 + barcodeMap,
  137 + byteArray,
  138 + bmWidth,
  139 + bmHeight
  140 + )
  141 + }.addOnFailureListener { e ->
144 mobileScannerErrorCallback( 142 mobileScannerErrorCallback(
145 e.localizedMessage ?: e.toString() 143 e.localizedMessage ?: e.toString()
146 ) 144 )
147 - }  
148 - .addOnCompleteListener { imageProxy.close() } 145 + }.addOnCompleteListener { imageProxy.close() }
  146 + }
149 147
150 if (detectionSpeed == DetectionSpeed.NORMAL) { 148 if (detectionSpeed == DetectionSpeed.NORMAL) {
151 // Set timer and continue 149 // Set timer and continue