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 @@
package="dev.steenbakker.mobile_scanner">
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
</manifest>
... ...
... ... @@ -28,7 +28,6 @@ import android.view.WindowManager
import android.content.Context
import android.os.Build
class MobileScanner(
private val activity: Activity,
private val textureRegistry: TextureRegistry,
... ... @@ -213,6 +212,7 @@ class MobileScanner(
torchStateCallback: TorchStateCallback,
zoomScaleStateCallback: ZoomScaleStateCallback,
mobileScannerStartedCallback: MobileScannerStartedCallback,
mobileScannerErrorCallback: (exception: Exception) -> Unit,
detectionTimeout: Long,
cameraResolution: Size?
) {
... ... @@ -221,7 +221,9 @@ class MobileScanner(
this.returnImage = returnImage
if (camera?.cameraInfo != null && preview != null && textureEntry != null) {
throw AlreadyStarted()
mobileScannerErrorCallback(AlreadyStarted())
return
}
scanner = if (barcodeScannerOptions != null) {
... ... @@ -235,10 +237,14 @@ class MobileScanner(
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
if (cameraProvider == null) {
throw CameraError()
mobileScannerErrorCallback(CameraError())
return@addListener
}
cameraProvider!!.unbindAll()
cameraProvider?.unbindAll()
textureEntry = textureRegistry.createSurfaceTexture()
// Preview
... ... @@ -290,38 +296,47 @@ class MobileScanner(
val analysis = analysisBuilder.build().apply { setAnalyzer(executor, captureOutput) }
camera = cameraProvider!!.bindToLifecycle(
try {
camera = cameraProvider?.bindToLifecycle(
activity as LifecycleOwner,
cameraPosition,
preview,
analysis
)
} catch(exception: Exception) {
mobileScannerErrorCallback(NoCamera())
return@addListener
}
camera?.let {
// Register the torch listener
camera!!.cameraInfo.torchState.observe(activity) { state ->
it.cameraInfo.torchState.observe(activity as LifecycleOwner) { state ->
// TorchState.OFF = 0; TorchState.ON = 1
torchStateCallback(state)
}
// Register the zoom scale listener
camera!!.cameraInfo.zoomState.observe(activity) { state ->
it.cameraInfo.zoomState.observe(activity) { state ->
zoomScaleStateCallback(state.linearZoom.toDouble())
}
// Enable torch if provided
camera!!.cameraControl.enableTorch(torch)
if (it.cameraInfo.hasFlashUnit()) {
it.cameraControl.enableTorch(torch)
}
}
val resolution = analysis.resolutionInfo!!.resolution
val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0
val width = resolution.width.toDouble()
val height = resolution.height.toDouble()
val portrait = (camera?.cameraInfo?.sensorRotationDegrees ?: 0) % 180 == 0
mobileScannerStartedCallback(
MobileScannerStartParameters(
if (portrait) width else height,
if (portrait) height else width,
camera!!.cameraInfo.hasFlashUnit(),
camera?.cameraInfo?.hasFlashUnit() ?: false,
textureEntry!!.id()
)
)
... ... @@ -363,7 +378,10 @@ class MobileScanner(
if (camera == null) {
throw TorchWhenStopped()
}
camera!!.cameraControl.enableTorch(enableTorch)
if (camera?.cameraInfo?.hasFlashUnit() == true) {
camera?.cameraControl?.enableTorch(enableTorch)
}
}
/**
... ... @@ -393,9 +411,9 @@ class MobileScanner(
* Set the zoom rate of the camera.
*/
fun setScale(scale: Double) {
if (camera == null) throw ZoomWhenStopped()
if (scale > 1.0 || scale < 0) throw ZoomNotInRange()
camera!!.cameraControl.setLinearZoom(scale.toFloat())
if (camera == null) throw ZoomWhenStopped()
camera?.cameraControl?.setLinearZoom(scale.toFloat())
}
/**
... ... @@ -403,7 +421,7 @@ class MobileScanner(
*/
fun resetScale() {
if (camera == null) throw ZoomWhenStopped()
camera!!.cameraControl.setZoomRatio(1f)
camera?.cameraControl?.setZoomRatio(1f)
}
}
... ...
... ... @@ -3,7 +3,6 @@ package dev.steenbakker.mobile_scanner
class NoCamera : Exception()
class AlreadyStarted : Exception()
class AlreadyStopped : Exception()
class TorchError : Exception()
class CameraError : Exception()
class TorchWhenStopped : Exception()
class ZoomWhenStopped : Exception()
... ...
... ... @@ -165,7 +165,6 @@ class MobileScannerHandler(
val detectionSpeed: DetectionSpeed = DetectionSpeed.values().first { it.intValue == speed}
try {
mobileScanner!!.start(
barcodeScannerOptions,
returnImage,
... ... @@ -183,41 +182,44 @@ class MobileScannerHandler(
))
}
},
timeout.toLong(),
cameraResolution,
)
} catch (e: AlreadyStarted) {
mobileScannerErrorCallback = {
Handler(Looper.getMainLooper()).post {
when (it) {
is AlreadyStarted -> {
result.error(
"MobileScanner",
"Called start() while already started",
null
)
} catch (e: NoCamera) {
result.error(
"MobileScanner",
"No camera found or failed to open camera!",
null
)
} catch (e: TorchError) {
}
is CameraError -> {
result.error(
"MobileScanner",
"Error occurred when setting torch!",
"Error occurred when setting up camera!",
null
)
} catch (e: CameraError) {
}
is NoCamera -> {
result.error(
"MobileScanner",
"Error occurred when setting up camera!",
"No camera found or failed to open camera!",
null
)
} catch (e: Exception) {
}
else -> {
result.error(
"MobileScanner",
"Unknown error occurred..",
"Unknown error occurred.",
null
)
}
}
}
},
timeout.toLong(),
cameraResolution,
)
}
private fun stop(result: MethodChannel.Result) {
try {
... ... @@ -238,7 +240,7 @@ class MobileScannerHandler(
try {
mobileScanner!!.toggleTorch(call.arguments == 1)
result.success(null)
} catch (e: AlreadyStopped) {
} catch (e: TorchWhenStopped) {
result.error("MobileScanner", "Called toggleTorch() while stopped!", null)
}
}
... ...
... ... @@ -305,7 +305,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
/// Set the zoom factor of the camera
func setScale(_ scale: CGFloat) throws {
if (device == nil) {
throw MobileScannerError.torchWhenStopped
throw MobileScannerError.zoomWhenStopped
}
do {
... ...
... ... @@ -95,7 +95,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
}
}
/// Parses all parameters and starts the mobileScanner
/// Start the mobileScanner.
private func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
let torch: Bool = (call.arguments as! Dictionary<String, Any?>)["torch"] as? Bool ?? false
let facing: Int = (call.arguments as! Dictionary<String, Any?>)["facing"] as? Int ?? 1
... ... @@ -151,7 +151,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
}
}
/// Stops the mobileScanner and closes the texture
/// Stops the mobileScanner and closes the texture.
private func stop(_ result: @escaping FlutterResult) {
do {
try mobileScanner.stop()
... ... @@ -159,7 +159,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
result(nil)
}
/// Toggles the torch
/// Toggles the torch.
private func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
do {
try mobileScanner.toggleTorch(call.arguments as? Int == 1 ? .on : .off)
... ... @@ -171,7 +171,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
}
}
/// Toggles the zoomScale
/// Sets the zoomScale.
private func setScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
let scale = call.arguments as? CGFloat
if (scale == nil) {
... ... @@ -198,7 +198,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
}
}
/// Reset the zoomScale
/// Reset the zoomScale.
private func resetScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
do {
try mobileScanner.resetScale()
... ... @@ -218,7 +218,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
}
}
/// Toggles the torch
/// Updates the scan window rectangle.
func updateScanWindow(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
let scanWindowData: Array? = (call.arguments as? [String: Any])?["rect"] as? [CGFloat]
MobileScannerPlugin.scanWindow = scanWindowData
... ... @@ -240,7 +240,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
return CGRect(x: minX, y: minY, width: width, height: height)
}
/// Analyzes a single image
/// Analyzes a single image.
private func analyzeImage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
let uiImage = UIImage(contentsOfFile: call.arguments as? String ?? "")
... ...