Commit b8f7c330 authored by Matyáš Latner's avatar Matyáš Latner

#627 IOS QR code scanner now handles permissions

parent 7a48fc6f
......@@ -28,38 +28,38 @@ public abstract class QRCodeScanner {
public interface QRCodeListener {
void onCodeScanned(String data);
}
private QRCodeListener listener;
private boolean scanningEnabled = false;
private boolean cameraPreviewActive = false;
private QRCodeListener listener;
private boolean scanningEnabled = false;
protected boolean cameraPreviewActive = false;
public void enableScanning() {
scanningEnabled = true;
}
public void disableScanning() {
scanningEnabled = false;
}
public boolean isScanningEnabled() {
return scanningEnabled;
}
public boolean isCameraPreviewActive() {
return cameraPreviewActive;
}
public void setQRCodeListener(QRCodeListener listener) {
this.listener = listener;
}
public void removeQRCodeListener() {
this.listener = null;
}
public synchronized void onCodeScanned(final String data) {
if(!isScanningEnabled()) return;
if(listener != null) {
Gdx.app.postRunnable(new Runnable() {
@Override
......@@ -67,34 +67,34 @@ public abstract class QRCodeScanner {
});
}
}
public void startCameraPreview() {
if(isCameraPreviewActive()) {
Log.info(getClass(), "Camera preview is already active. Cannot start it again!");
return;
}
Log.info(getClass(), "Starting Camera Preview!");
cameraPreviewActive = true;
onCameraPreviewStarted();
}
public void stopCameraPreview() {
if(!isCameraPreviewActive()) {
Log.info(getClass(), "Camera preview is not running. Cannot stop it!");
return;
}
Log.info(getClass(), "Stopping Camera Preview");
onCameraPreviewStopped();
cameraPreviewActive = false;
}
/* ABSTRACT METHODS */
public abstract boolean isCameraAccessible();
public abstract void onCameraPreviewStarted();
public abstract void onCameraPreviewStopped();
}
......@@ -20,6 +20,7 @@ package cz.nic.tablexia;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.iosrobovm.IOSApplication;
import org.robovm.apple.avfoundation.AVAuthorizationStatus;
import org.robovm.apple.avfoundation.AVCaptureConnection;
import org.robovm.apple.avfoundation.AVCaptureDevice;
import org.robovm.apple.avfoundation.AVCaptureDeviceInput;
......@@ -38,27 +39,30 @@ import org.robovm.apple.dispatch.DispatchQueue;
import org.robovm.apple.dispatch.DispatchQueueAttr;
import org.robovm.apple.foundation.NSArray;
import org.robovm.apple.foundation.NSErrorException;
import org.robovm.apple.foundation.NSString;
import org.robovm.apple.uikit.UIApplication;
import org.robovm.apple.uikit.UIInterfaceOrientation;
import org.robovm.apple.uikit.UIView;
import org.robovm.apple.uikit.UIViewController;
import org.robovm.objc.block.VoidBooleanBlock;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import cz.nic.tablexia.util.Log;
import cz.nic.tablexia.util.ui.QRCodeScanner;
public class IOSQRCodeScanner extends QRCodeScanner {
private static final String DISPATCH_QUEUE_NAME = "qrCodeQueue";
private class CaptureMetadataOutputDelegate extends AVCaptureMetadataOutputObjectsDelegateAdapter{
private IOSQRCodeScanner qrCodeScanner;
public CaptureMetadataOutputDelegate() {
this.qrCodeScanner = IOSQRCodeScanner.this;
}
@Override
public void didOutputMetadataObjects(AVCaptureOutput captureOutput, NSArray<AVMetadataObject> metadataObjects, AVCaptureConnection connection) {
if(metadataObjects != null && metadataObjects.size() > 0) {
......@@ -67,46 +71,84 @@ public class IOSQRCodeScanner extends QRCodeScanner {
//Found QR Code TODO - change data.toString()
qrCodeScanner.onCodeScanned(data.toString());
}
metadataObjects.dispose();
}
}
}
private AVCaptureDevice cameraDevice;
private AVCaptureSession captureSession;
private AVCaptureDeviceInput captureDeviceInput;
private AVCaptureMetadataOutput captureMetadataOutput;
private CaptureMetadataOutputDelegate captureDelegate;
private UIViewController cameraPreviewViewController;
private UIView cameraPreviewView;
private AVCaptureVideoPreviewLayer videoPreviewLayer;
private DispatchQueue dispatchQueue;
private AtomicBoolean hasPermissions = new AtomicBoolean(false);
private AtomicBoolean startRequested = new AtomicBoolean(false);
private void initializeCameraDevice() {
if(cameraDevice != null) return;
NSArray<AVCaptureDevice> captureDevices = AVCaptureDevice.getDevicesForMediaType(AVMediaType.Video);
for(AVCaptureDevice captureDevice : captureDevices) {
if(captureDevice.getPosition() == AVCaptureDevicePosition.Back) {
//We found back camera!
cameraDevice = captureDevice;
return;
break;
}
}
}
private void setupPermissionsToUseCamera() {
if(cameraDevice == null) {
hasPermissions.set(false);
return;
}
AVAuthorizationStatus status = cameraDevice.getAuthorizationStatusForMediaType(AVMediaType.Video);
Log.info(getClass(), "Authorization status for video media type: " + status.name());
switch (status) {
case Authorized:
hasPermissions.set(true);
break;
case NotDetermined:
cameraDevice.requestAccessForMediaType(AVMediaType.Video, new VoidBooleanBlock() {
@Override
public void invoke(boolean b) {
hasPermissions.set(b);
if(hasPermissions.get() && startRequested.get() && !isCameraPreviewActive()) {
Log.info(getClass(), "Starting camera preview again!");
startCameraPreview();
}
}
});
break;
case Restricted:
case Denied:
hasPermissions.set(false);
return;
}
}
@Override
public boolean isCameraAccessible() {
initializeCameraDevice();
return cameraDevice != null;
}
private AVCaptureVideoOrientation getOrientationForCameraPreviewLayer() {
switch (UIApplication.getSharedApplication().getStatusBarOrientation()) {
case LandscapeLeft: return AVCaptureVideoOrientation.LandscapeLeft;
......@@ -116,43 +158,56 @@ public class IOSQRCodeScanner extends QRCodeScanner {
default: return AVCaptureVideoOrientation.LandscapeLeft;
}
}
private void updateViewPreviewLayerOrientation() {
if(videoPreviewLayer != null ) videoPreviewLayer.getConnection().setVideoOrientation(getOrientationForCameraPreviewLayer());
}
@Override
public void onCameraPreviewStarted() {
captureSession = new AVCaptureSession();
initializeCameraDevice();
setupPermissionsToUseCamera();
if(!hasPermissions.get()) {
cameraPreviewActive = false;
startRequested.set(true);
captureSession.dispose();
captureSession = null;
return;
}
//INPUT DEVICE//
try {
captureDeviceInput = new AVCaptureDeviceInput(cameraDevice);
if(captureSession.canAddInput(captureDeviceInput)) captureSession.addInput(captureDeviceInput);
} catch (NSErrorException e) {
Log.err(getClass(), "Cannot start camera preview!", e);
if(captureDeviceInput != null) captureDeviceInput.dispose();
captureSession.dispose();
return;
}
dispatchQueue = DispatchQueue.create(DISPATCH_QUEUE_NAME, DispatchQueueAttr.Serial());
captureMetadataOutput = new AVCaptureMetadataOutput();
captureDelegate = new CaptureMetadataOutputDelegate();
captureMetadataOutput.setMetadataObjectsDelegate(captureDelegate, dispatchQueue);
if(captureSession.canAddOutput(captureMetadataOutput)) captureSession.addOutput(captureMetadataOutput);
captureMetadataOutput.setMetadataObjectTypes(Arrays.asList(AVMetadataObjectType.QRCode));
IOSApplication iosApplication = (IOSApplication) Gdx.app;
cameraPreviewViewController = new UIViewController() {
@Override
public void viewWillLayoutSubviews() {
super.viewWillLayoutSubviews();
updateViewPreviewLayerOrientation();
}
@Override
public void willAnimateRotation(UIInterfaceOrientation uiInterfaceOrientation, double v) {
//Unforunately this method is deprecated and there isnt binding in RoboVM for these replacements...
......@@ -162,57 +217,57 @@ public class IOSQRCodeScanner extends QRCodeScanner {
updateViewPreviewLayerOrientation();
}
};
cameraPreviewView = new UIView();
cameraPreviewView.setBounds(iosApplication.getUIViewController().getView().getBounds());
cameraPreviewView.setCenter(iosApplication.getUIViewController().getView().getCenter());
cameraPreviewViewController.setView(cameraPreviewView);
videoPreviewLayer = new AVCaptureVideoPreviewLayer(captureSession);
videoPreviewLayer.setVideoGravity(AVLayerVideoGravity.ResizeAspectFill);
videoPreviewLayer.getConnection().setVideoOrientation(getOrientationForCameraPreviewLayer());
videoPreviewLayer.setFrame(cameraPreviewView.getBounds());
cameraPreviewView.getLayer().addSublayer(videoPreviewLayer);
iosApplication.getUIViewController().addChildViewController(cameraPreviewViewController);
iosApplication.getUIViewController().getView().addSubview(cameraPreviewView);
if(!captureSession.isRunning()) captureSession.startRunning();
}
@Override
public void onCameraPreviewStopped() {
captureSession.stopRunning();
captureSession.removeInput(captureDeviceInput);
captureSession.removeOutput(captureMetadataOutput);
captureDeviceInput.dispose();
captureDeviceInput = null;
captureMetadataOutput.dispose();
captureMetadataOutput = null;
captureDelegate.dispose();
captureDelegate = null;
videoPreviewLayer.removeFromSuperlayer();
videoPreviewLayer.dispose();
videoPreviewLayer = null;
cameraPreviewViewController.removeFromParentViewController();
cameraPreviewViewController.dispose();
cameraPreviewViewController = null;
cameraPreviewView.removeFromSuperview();
cameraPreviewView.dispose();
cameraPreviewView = null;
captureSession.dispose();
captureSession = null;
dispatchQueue = null;
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment