Newer
Older
/*
* Copyright (C) 2016 CZ.NIC, z.s.p.o. (http://www.nic.cz/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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;
import org.robovm.apple.avfoundation.AVCaptureDevicePosition;
import org.robovm.apple.avfoundation.AVCaptureMetadataOutput;
import org.robovm.apple.avfoundation.AVCaptureMetadataOutputObjectsDelegateAdapter;
import org.robovm.apple.avfoundation.AVCaptureOutput;
import org.robovm.apple.avfoundation.AVCaptureSession;

Drahomír Karchňák
committed
import org.robovm.apple.avfoundation.AVCaptureVideoOrientation;
import org.robovm.apple.avfoundation.AVCaptureVideoPreviewLayer;

Drahomír Karchňák
committed
import org.robovm.apple.avfoundation.AVLayerVideoGravity;
import org.robovm.apple.avfoundation.AVMediaType;
import org.robovm.apple.avfoundation.AVMetadataObject;
import org.robovm.apple.avfoundation.AVMetadataObjectType;
import org.robovm.apple.dispatch.DispatchQueue;

Drahomír Karchňák
committed
import org.robovm.apple.dispatch.DispatchQueueAttr;
import org.robovm.apple.foundation.NSArray;
import org.robovm.apple.foundation.NSErrorException;
import org.robovm.apple.foundation.NSString;

Drahomír Karchňák
committed
import org.robovm.apple.uikit.UIApplication;
import org.robovm.apple.uikit.UIInterfaceOrientation;
import org.robovm.apple.uikit.UIView;

Drahomír Karchňák
committed
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.ui.QRCodeScanner;
public class IOSQRCodeScanner extends QRCodeScanner {

Drahomír Karchňák
committed
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) {
AVMetadataObject data = metadataObjects.get(0);
if(data.getType() == AVMetadataObjectType.QRCode) {
//Found QR Code TODO - change data.toString()
qrCodeScanner.onCodeScanned(data.toString());
}

Drahomír Karchňák
committed
metadataObjects.dispose();
private AVCaptureDevice cameraDevice;
private AVCaptureSession captureSession;
private AVCaptureDeviceInput captureDeviceInput;
private AVCaptureMetadataOutput captureMetadataOutput;
private CaptureMetadataOutputDelegate captureDelegate;

Drahomír Karchňák
committed
private UIViewController cameraPreviewViewController;
private UIView cameraPreviewView;

Drahomír Karchňák
committed
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;
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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;
}

Drahomír Karchňák
committed
private AVCaptureVideoOrientation getOrientationForCameraPreviewLayer() {
switch (UIApplication.getSharedApplication().getStatusBarOrientation()) {
case LandscapeLeft: return AVCaptureVideoOrientation.LandscapeLeft;
case LandscapeRight: return AVCaptureVideoOrientation.LandscapeRight;
case Portrait: return AVCaptureVideoOrientation.Portrait;
case PortraitUpsideDown: return AVCaptureVideoOrientation.PortraitUpsideDown;
default: return AVCaptureVideoOrientation.LandscapeLeft;
}
}

Drahomír Karchňák
committed
private void updateViewPreviewLayerOrientation() {
if(videoPreviewLayer != null ) videoPreviewLayer.getConnection().setVideoOrientation(getOrientationForCameraPreviewLayer());
}

Drahomír Karchňák
committed
@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;

Drahomír Karchňák
committed
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;

Drahomír Karchňák
committed
cameraPreviewViewController = new UIViewController() {
@Override
public void viewWillLayoutSubviews() {
super.viewWillLayoutSubviews();
updateViewPreviewLayerOrientation();
}

Drahomír Karchňák
committed
@Override
public void willAnimateRotation(UIInterfaceOrientation uiInterfaceOrientation, double v) {
//Unforunately this method is deprecated and there isnt binding in RoboVM for these replacements...
// - willTransitionToTraitCollection:withTransitionCoordinator:
// - viewWillTransitionToSize:withTransitionCoordinator:
super.willAnimateRotation(uiInterfaceOrientation, v);
updateViewPreviewLayerOrientation();
}
};
cameraPreviewView = new UIView();
cameraPreviewView.setBounds(iosApplication.getUIViewController().getView().getBounds());
cameraPreviewView.setCenter(iosApplication.getUIViewController().getView().getCenter());

Drahomír Karchňák
committed
cameraPreviewViewController.setView(cameraPreviewView);

Drahomír Karchňák
committed
videoPreviewLayer = new AVCaptureVideoPreviewLayer(captureSession);

Drahomír Karchňák
committed
videoPreviewLayer.setVideoGravity(AVLayerVideoGravity.ResizeAspectFill);
videoPreviewLayer.getConnection().setVideoOrientation(getOrientationForCameraPreviewLayer());
videoPreviewLayer.setFrame(cameraPreviewView.getBounds());
cameraPreviewView.getLayer().addSublayer(videoPreviewLayer);

Drahomír Karchňák
committed
iosApplication.getUIViewController().addChildViewController(cameraPreviewViewController);
iosApplication.getUIViewController().getView().addSubview(cameraPreviewView);
if(!captureSession.isRunning()) captureSession.startRunning();
}
public void onCameraPreviewStopped() {
captureSession.removeInput(captureDeviceInput);
captureSession.removeOutput(captureMetadataOutput);

Drahomír Karchňák
committed
captureDeviceInput.dispose();

Drahomír Karchňák
committed
captureMetadataOutput.dispose();

Drahomír Karchňák
committed
captureDelegate.dispose();

Drahomír Karchňák
committed
videoPreviewLayer.removeFromSuperlayer();

Drahomír Karchňák
committed
videoPreviewLayer.dispose();

Drahomír Karchňák
committed
videoPreviewLayer = null;

Drahomír Karchňák
committed
cameraPreviewViewController.removeFromParentViewController();
cameraPreviewViewController.dispose();
cameraPreviewViewController = null;
cameraPreviewView.removeFromSuperview();

Drahomír Karchňák
committed
cameraPreviewView.dispose();

Drahomír Karchňák
committed
captureSession.dispose();

Drahomír Karchňák
committed
captureSession = null;

Drahomír Karchňák
committed
dispatchQueue = null;