Skip to content
Snippets Groups Projects
IOSQRCodeScanner.java 10.8 KiB
Newer Older
Matyáš Latner's avatar
Matyáš Latner committed
/*
 * 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;
import org.robovm.apple.avfoundation.AVCaptureVideoOrientation;
import org.robovm.apple.avfoundation.AVCaptureVideoPreviewLayer;
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;
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.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) {
                AVMetadataObject data = metadataObjects.get(0);
                if(data.getType() == AVMetadataObjectType.QRCode) {
                    //Found QR Code TODO - change data.toString()
                    qrCodeScanner.onCodeScanned(data.toString());
                }
    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;
    
    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;
            case LandscapeRight: return AVCaptureVideoOrientation.LandscapeRight;
            case Portrait: return AVCaptureVideoOrientation.Portrait;
            case PortraitUpsideDown: return AVCaptureVideoOrientation.PortraitUpsideDown;
            default: return AVCaptureVideoOrientation.LandscapeLeft;
        }
    }
    private void updateViewPreviewLayerOrientation() {
        if(videoPreviewLayer != null ) videoPreviewLayer.getConnection().setVideoOrientation(getOrientationForCameraPreviewLayer());
    }
        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...
                // - willTransitionToTraitCollection:withTransitionCoordinator:
                // - viewWillTransitionToSize:withTransitionCoordinator:
                super.willAnimateRotation(uiInterfaceOrientation, v);
                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();
    }
    public void onCameraPreviewStopped() {
        captureSession.stopRunning();
        captureSession.removeInput(captureDeviceInput);
        captureSession.removeOutput(captureMetadataOutput);
        captureDeviceInput = null;
        captureMetadataOutput = null;
        captureDelegate = null;
        cameraPreviewViewController.removeFromParentViewController();
        cameraPreviewViewController.dispose();
        cameraPreviewViewController = null;
        cameraPreviewView.removeFromSuperview();
        cameraPreviewView = null;