Navaron Bracke
Committed by GitHub

Merge pull request #838 from navaronbracke/fix_android_no_camera

fix: Handle no cameras on Android
@@ -2,4 +2,5 @@ @@ -2,4 +2,5 @@
2 package="dev.steenbakker.mobile_scanner"> 2 package="dev.steenbakker.mobile_scanner">
3 3
4 <uses-permission android:name="android.permission.CAMERA" /> 4 <uses-permission android:name="android.permission.CAMERA" />
  5 + <uses-feature android:name="android.hardware.camera" android:required="false" />
5 </manifest> 6 </manifest>
@@ -28,7 +28,6 @@ import android.view.WindowManager @@ -28,7 +28,6 @@ import android.view.WindowManager
28 import android.content.Context 28 import android.content.Context
29 import android.os.Build 29 import android.os.Build
30 30
31 -  
32 class MobileScanner( 31 class MobileScanner(
33 private val activity: Activity, 32 private val activity: Activity,
34 private val textureRegistry: TextureRegistry, 33 private val textureRegistry: TextureRegistry,
@@ -213,6 +212,7 @@ class MobileScanner( @@ -213,6 +212,7 @@ class MobileScanner(
213 torchStateCallback: TorchStateCallback, 212 torchStateCallback: TorchStateCallback,
214 zoomScaleStateCallback: ZoomScaleStateCallback, 213 zoomScaleStateCallback: ZoomScaleStateCallback,
215 mobileScannerStartedCallback: MobileScannerStartedCallback, 214 mobileScannerStartedCallback: MobileScannerStartedCallback,
  215 + mobileScannerErrorCallback: (exception: Exception) -> Unit,
216 detectionTimeout: Long, 216 detectionTimeout: Long,
217 cameraResolution: Size? 217 cameraResolution: Size?
218 ) { 218 ) {
@@ -221,7 +221,9 @@ class MobileScanner( @@ -221,7 +221,9 @@ class MobileScanner(
221 this.returnImage = returnImage 221 this.returnImage = returnImage
222 222
223 if (camera?.cameraInfo != null && preview != null && textureEntry != null) { 223 if (camera?.cameraInfo != null && preview != null && textureEntry != null) {
224 - throw AlreadyStarted() 224 + mobileScannerErrorCallback(AlreadyStarted())
  225 +
  226 + return
225 } 227 }
226 228
227 scanner = if (barcodeScannerOptions != null) { 229 scanner = if (barcodeScannerOptions != null) {
@@ -235,10 +237,14 @@ class MobileScanner( @@ -235,10 +237,14 @@ class MobileScanner(
235 237
236 cameraProviderFuture.addListener({ 238 cameraProviderFuture.addListener({
237 cameraProvider = cameraProviderFuture.get() 239 cameraProvider = cameraProviderFuture.get()
  240 +
238 if (cameraProvider == null) { 241 if (cameraProvider == null) {
239 - throw CameraError() 242 + mobileScannerErrorCallback(CameraError())
  243 +
  244 + return@addListener
240 } 245 }
241 - cameraProvider!!.unbindAll() 246 +
  247 + cameraProvider?.unbindAll()
242 textureEntry = textureRegistry.createSurfaceTexture() 248 textureEntry = textureRegistry.createSurfaceTexture()
243 249
244 // Preview 250 // Preview
@@ -290,38 +296,47 @@ class MobileScanner( @@ -290,38 +296,47 @@ class MobileScanner(
290 296
291 val analysis = analysisBuilder.build().apply { setAnalyzer(executor, captureOutput) } 297 val analysis = analysisBuilder.build().apply { setAnalyzer(executor, captureOutput) }
292 298
293 - camera = cameraProvider!!.bindToLifecycle( 299 + try {
  300 + camera = cameraProvider?.bindToLifecycle(
294 activity as LifecycleOwner, 301 activity as LifecycleOwner,
295 cameraPosition, 302 cameraPosition,
296 preview, 303 preview,
297 analysis 304 analysis
298 ) 305 )
  306 + } catch(exception: Exception) {
  307 + mobileScannerErrorCallback(NoCamera())
  308 +
  309 + return@addListener
  310 + }
299 311
  312 + camera?.let {
300 // Register the torch listener 313 // Register the torch listener
301 - camera!!.cameraInfo.torchState.observe(activity) { state -> 314 + it.cameraInfo.torchState.observe(activity as LifecycleOwner) { state ->
302 // TorchState.OFF = 0; TorchState.ON = 1 315 // TorchState.OFF = 0; TorchState.ON = 1
303 torchStateCallback(state) 316 torchStateCallback(state)
304 } 317 }
305 318
306 // Register the zoom scale listener 319 // Register the zoom scale listener
307 - camera!!.cameraInfo.zoomState.observe(activity) { state -> 320 + it.cameraInfo.zoomState.observe(activity) { state ->
308 zoomScaleStateCallback(state.linearZoom.toDouble()) 321 zoomScaleStateCallback(state.linearZoom.toDouble())
309 } 322 }
310 323
311 -  
312 // Enable torch if provided 324 // Enable torch if provided
313 - camera!!.cameraControl.enableTorch(torch) 325 + if (it.cameraInfo.hasFlashUnit()) {
  326 + it.cameraControl.enableTorch(torch)
  327 + }
  328 + }
314 329
315 val resolution = analysis.resolutionInfo!!.resolution 330 val resolution = analysis.resolutionInfo!!.resolution
316 - val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0  
317 val width = resolution.width.toDouble() 331 val width = resolution.width.toDouble()
318 val height = resolution.height.toDouble() 332 val height = resolution.height.toDouble()
  333 + val portrait = (camera?.cameraInfo?.sensorRotationDegrees ?: 0) % 180 == 0
319 334
320 mobileScannerStartedCallback( 335 mobileScannerStartedCallback(
321 MobileScannerStartParameters( 336 MobileScannerStartParameters(
322 if (portrait) width else height, 337 if (portrait) width else height,
323 if (portrait) height else width, 338 if (portrait) height else width,
324 - camera!!.cameraInfo.hasFlashUnit(), 339 + camera?.cameraInfo?.hasFlashUnit() ?: false,
325 textureEntry!!.id() 340 textureEntry!!.id()
326 ) 341 )
327 ) 342 )
@@ -363,7 +378,10 @@ class MobileScanner( @@ -363,7 +378,10 @@ class MobileScanner(
363 if (camera == null) { 378 if (camera == null) {
364 throw TorchWhenStopped() 379 throw TorchWhenStopped()
365 } 380 }
366 - camera!!.cameraControl.enableTorch(enableTorch) 381 +
  382 + if (camera?.cameraInfo?.hasFlashUnit() == true) {
  383 + camera?.cameraControl?.enableTorch(enableTorch)
  384 + }
367 } 385 }
368 386
369 /** 387 /**
@@ -393,9 +411,9 @@ class MobileScanner( @@ -393,9 +411,9 @@ class MobileScanner(
393 * Set the zoom rate of the camera. 411 * Set the zoom rate of the camera.
394 */ 412 */
395 fun setScale(scale: Double) { 413 fun setScale(scale: Double) {
396 - if (camera == null) throw ZoomWhenStopped()  
397 if (scale > 1.0 || scale < 0) throw ZoomNotInRange() 414 if (scale > 1.0 || scale < 0) throw ZoomNotInRange()
398 - camera!!.cameraControl.setLinearZoom(scale.toFloat()) 415 + if (camera == null) throw ZoomWhenStopped()
  416 + camera?.cameraControl?.setLinearZoom(scale.toFloat())
399 } 417 }
400 418
401 /** 419 /**
@@ -403,7 +421,7 @@ class MobileScanner( @@ -403,7 +421,7 @@ class MobileScanner(
403 */ 421 */
404 fun resetScale() { 422 fun resetScale() {
405 if (camera == null) throw ZoomWhenStopped() 423 if (camera == null) throw ZoomWhenStopped()
406 - camera!!.cameraControl.setZoomRatio(1f) 424 + camera?.cameraControl?.setZoomRatio(1f)
407 } 425 }
408 426
409 } 427 }
@@ -3,7 +3,6 @@ package dev.steenbakker.mobile_scanner @@ -3,7 +3,6 @@ package dev.steenbakker.mobile_scanner
3 class NoCamera : Exception() 3 class NoCamera : Exception()
4 class AlreadyStarted : Exception() 4 class AlreadyStarted : Exception()
5 class AlreadyStopped : Exception() 5 class AlreadyStopped : Exception()
6 -class TorchError : Exception()  
7 class CameraError : Exception() 6 class CameraError : Exception()
8 class TorchWhenStopped : Exception() 7 class TorchWhenStopped : Exception()
9 class ZoomWhenStopped : Exception() 8 class ZoomWhenStopped : Exception()
@@ -165,7 +165,6 @@ class MobileScannerHandler( @@ -165,7 +165,6 @@ class MobileScannerHandler(
165 165
166 val detectionSpeed: DetectionSpeed = DetectionSpeed.values().first { it.intValue == speed} 166 val detectionSpeed: DetectionSpeed = DetectionSpeed.values().first { it.intValue == speed}
167 167
168 - try {  
169 mobileScanner!!.start( 168 mobileScanner!!.start(
170 barcodeScannerOptions, 169 barcodeScannerOptions,
171 returnImage, 170 returnImage,
@@ -183,41 +182,44 @@ class MobileScannerHandler( @@ -183,41 +182,44 @@ class MobileScannerHandler(
183 )) 182 ))
184 } 183 }
185 }, 184 },
186 - timeout.toLong(),  
187 - cameraResolution,  
188 - )  
189 - } catch (e: AlreadyStarted) { 185 + mobileScannerErrorCallback = {
  186 + Handler(Looper.getMainLooper()).post {
  187 + when (it) {
  188 + is AlreadyStarted -> {
190 result.error( 189 result.error(
191 "MobileScanner", 190 "MobileScanner",
192 "Called start() while already started", 191 "Called start() while already started",
193 null 192 null
194 ) 193 )
195 - } catch (e: NoCamera) {  
196 - result.error(  
197 - "MobileScanner",  
198 - "No camera found or failed to open camera!",  
199 - null  
200 - )  
201 - } catch (e: TorchError) { 194 + }
  195 + is CameraError -> {
202 result.error( 196 result.error(
203 "MobileScanner", 197 "MobileScanner",
204 - "Error occurred when setting torch!", 198 + "Error occurred when setting up camera!",
205 null 199 null
206 ) 200 )
207 - } catch (e: CameraError) { 201 + }
  202 + is NoCamera -> {
208 result.error( 203 result.error(
209 "MobileScanner", 204 "MobileScanner",
210 - "Error occurred when setting up camera!", 205 + "No camera found or failed to open camera!",
211 null 206 null
212 ) 207 )
213 - } catch (e: Exception) { 208 + }
  209 + else -> {
214 result.error( 210 result.error(
215 "MobileScanner", 211 "MobileScanner",
216 - "Unknown error occurred..", 212 + "Unknown error occurred.",
217 null 213 null
218 ) 214 )
219 } 215 }
220 } 216 }
  217 + }
  218 + },
  219 + timeout.toLong(),
  220 + cameraResolution,
  221 + )
  222 + }
221 223
222 private fun stop(result: MethodChannel.Result) { 224 private fun stop(result: MethodChannel.Result) {
223 try { 225 try {
@@ -238,7 +240,7 @@ class MobileScannerHandler( @@ -238,7 +240,7 @@ class MobileScannerHandler(
238 try { 240 try {
239 mobileScanner!!.toggleTorch(call.arguments == 1) 241 mobileScanner!!.toggleTorch(call.arguments == 1)
240 result.success(null) 242 result.success(null)
241 - } catch (e: AlreadyStopped) { 243 + } catch (e: TorchWhenStopped) {
242 result.error("MobileScanner", "Called toggleTorch() while stopped!", null) 244 result.error("MobileScanner", "Called toggleTorch() while stopped!", null)
243 } 245 }
244 } 246 }
@@ -305,7 +305,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -305,7 +305,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
305 /// Set the zoom factor of the camera 305 /// Set the zoom factor of the camera
306 func setScale(_ scale: CGFloat) throws { 306 func setScale(_ scale: CGFloat) throws {
307 if (device == nil) { 307 if (device == nil) {
308 - throw MobileScannerError.torchWhenStopped 308 + throw MobileScannerError.zoomWhenStopped
309 } 309 }
310 310
311 do { 311 do {
@@ -95,7 +95,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { @@ -95,7 +95,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
95 } 95 }
96 } 96 }
97 97
98 - /// Parses all parameters and starts the mobileScanner 98 + /// Start the mobileScanner.
99 private func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 99 private func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
100 let torch: Bool = (call.arguments as! Dictionary<String, Any?>)["torch"] as? Bool ?? false 100 let torch: Bool = (call.arguments as! Dictionary<String, Any?>)["torch"] as? Bool ?? false
101 let facing: Int = (call.arguments as! Dictionary<String, Any?>)["facing"] as? Int ?? 1 101 let facing: Int = (call.arguments as! Dictionary<String, Any?>)["facing"] as? Int ?? 1
@@ -151,7 +151,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { @@ -151,7 +151,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
151 } 151 }
152 } 152 }
153 153
154 - /// Stops the mobileScanner and closes the texture 154 + /// Stops the mobileScanner and closes the texture.
155 private func stop(_ result: @escaping FlutterResult) { 155 private func stop(_ result: @escaping FlutterResult) {
156 do { 156 do {
157 try mobileScanner.stop() 157 try mobileScanner.stop()
@@ -159,7 +159,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { @@ -159,7 +159,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
159 result(nil) 159 result(nil)
160 } 160 }
161 161
162 - /// Toggles the torch 162 + /// Toggles the torch.
163 private func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 163 private func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
164 do { 164 do {
165 try mobileScanner.toggleTorch(call.arguments as? Int == 1 ? .on : .off) 165 try mobileScanner.toggleTorch(call.arguments as? Int == 1 ? .on : .off)
@@ -171,7 +171,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { @@ -171,7 +171,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
171 } 171 }
172 } 172 }
173 173
174 - /// Toggles the zoomScale 174 + /// Sets the zoomScale.
175 private func setScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 175 private func setScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
176 let scale = call.arguments as? CGFloat 176 let scale = call.arguments as? CGFloat
177 if (scale == nil) { 177 if (scale == nil) {
@@ -198,7 +198,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { @@ -198,7 +198,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
198 } 198 }
199 } 199 }
200 200
201 - /// Reset the zoomScale 201 + /// Reset the zoomScale.
202 private func resetScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 202 private func resetScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
203 do { 203 do {
204 try mobileScanner.resetScale() 204 try mobileScanner.resetScale()
@@ -218,7 +218,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { @@ -218,7 +218,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
218 } 218 }
219 } 219 }
220 220
221 - /// Toggles the torch 221 + /// Updates the scan window rectangle.
222 func updateScanWindow(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 222 func updateScanWindow(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
223 let scanWindowData: Array? = (call.arguments as? [String: Any])?["rect"] as? [CGFloat] 223 let scanWindowData: Array? = (call.arguments as? [String: Any])?["rect"] as? [CGFloat]
224 MobileScannerPlugin.scanWindow = scanWindowData 224 MobileScannerPlugin.scanWindow = scanWindowData
@@ -240,7 +240,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { @@ -240,7 +240,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
240 return CGRect(x: minX, y: minY, width: width, height: height) 240 return CGRect(x: minX, y: minY, width: width, height: height)
241 } 241 }
242 242
243 - /// Analyzes a single image 243 + /// Analyzes a single image.
244 private func analyzeImage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 244 private func analyzeImage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
245 let uiImage = UIImage(contentsOfFile: call.arguments as? String ?? "") 245 let uiImage = UIImage(contentsOfFile: call.arguments as? String ?? "")
246 246