uk.ac.horizon.artcodes.camera.CameraView.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.horizon.artcodes.camera.CameraView.java

Source

/*
 * Artcodes recognises a different marker scheme that allows the
 * creation of aesthetically pleasing, even beautiful, codes.
 * Copyright (C) 2013-2016  The University of Nottingham
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 *     You should have received a copy of the GNU Affero General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package uk.ac.horizon.artcodes.camera;

import android.Manifest;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;

import java.io.IOException;
import java.util.List;

import uk.ac.horizon.artcodes.detect.Detector;
import uk.ac.horizon.artcodes.model.Experience;
import uk.ac.horizon.artcodes.scanner.R;
import uk.ac.horizon.artcodes.detect.DetectorSetting;

@SuppressWarnings("deprecation")
public class CameraView extends SurfaceView implements CameraFocusControl {
    private static final String THREAD_NAME = "Frame Processor";
    private CameraInfo info;
    private Camera camera;
    private int facing = Camera.CameraInfo.CAMERA_FACING_BACK;
    private Detector detector;
    private HandlerThread cameraThread;
    private SurfaceHolder surface;
    private int surfaceWidth;
    private int surfaceHeight;
    private boolean deviceNeedsTapToFocus = false;
    private final Object cameraFocusLock = new Object();
    private boolean cameraIsFocused = true;
    private Experience experience;

    private String debug_resolution;

    public CameraView(Context context) {
        super(context);
        init();
    }

    public CameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public CameraView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    public void setDetector(Detector processor) {
        setDetector(processor, null);
    }

    public void setDetector(Detector processor, Experience experience) {
        this.experience = experience;
        this.detector = processor;
        if (detector != null) {
            if (camera == null) {
                createCamera();
            }
            if (camera != null) {
                cameraIsFocused = !deviceNeedsTapToFocus;
                camera.addCallbackBuffer(detector.createBuffer(info, surfaceWidth, surfaceHeight));
                camera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
                    @Override
                    public void onPreviewFrame(final byte[] data, final Camera camera) {

                        boolean focusedOnThisFrame = false;

                        if (deviceNeedsTapToFocus || !cameraIsFocused) {
                            synchronized (cameraFocusLock) {
                                while (!cameraIsFocused) {
                                    try {
                                        Log.i("FOCUS", "cameraFocusLock.wait(); Thread: "
                                                + Thread.currentThread().getName());
                                        focusedOnThisFrame = true;
                                        cameraFocusLock.wait();
                                    } catch (InterruptedException ignored) {
                                    }
                                }
                                Log.i("FOCUS", "notified Thread: " + Thread.currentThread().getName());
                            }
                        }

                        if (cameraIsFocused) {
                            Log.i("FOCUS", "cameraIsFocused test: " + cameraIsFocused);
                            if (!focusedOnThisFrame) {
                                detector.setData(data);
                            }
                            camera.addCallbackBuffer(data);
                        }
                    }
                });
            }

            if (Camera.getNumberOfCameras() > 1) {
                detector.getSettings().add(new DetectorSetting() {
                    @Override
                    public void nextValue() {
                        stopCamera();
                        facing = 1 - facing;
                        startCamera();
                    }

                    @Override
                    public int getIcon() {
                        switch (facing) {
                        case Camera.CameraInfo.CAMERA_FACING_BACK:
                            return R.drawable.ic_camera_rear_24dp;
                        case Camera.CameraInfo.CAMERA_FACING_FRONT:
                            return R.drawable.ic_camera_front_24dp;
                        }
                        return 0;
                    }

                    @Override
                    public int getText() {
                        switch (facing) {
                        case Camera.CameraInfo.CAMERA_FACING_BACK:
                            return R.string.camera_rear;
                        case Camera.CameraInfo.CAMERA_FACING_FRONT:
                            return R.string.camera_front;
                        }

                        return 0;
                    }
                });
            }
        }
    }

    private void init() {
        getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                if (holder.getSurface() == null) {
                    Log.i("scanner", "No surface?");
                    return;
                }
                surface = holder;
                stopCamera();
                surfaceWidth = width;
                surfaceHeight = height;
                startCamera();
            }

            @Override
            public void surfaceCreated(SurfaceHolder holder) {
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                stopCamera();
            }
        });
    }

    private void createCamera() {
        if (ContextCompat.checkSelfPermission(getContext(),
                Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
            for (int cameraId = 0; cameraId < Camera.getNumberOfCameras(); cameraId++) {
                try {
                    Camera.CameraInfo info = new Camera.CameraInfo();
                    Camera.getCameraInfo(cameraId, info);

                    if (info.facing == facing) {
                        openCamera(cameraId);
                        return;
                    }
                } catch (RuntimeException e) {
                    Log.e("Scanner", "Failed to open scanner " + cameraId + ": " + e.getLocalizedMessage(), e);
                }
            }

            for (int cameraId = 0; cameraId < Camera.getNumberOfCameras(); cameraId++) {
                try {
                    openCamera(cameraId);
                    return;
                } catch (RuntimeException e) {
                    Log.e("Scanner", "Failed to open scanner " + cameraId + ": " + e.getLocalizedMessage(), e);
                }
            }
        }
    }

    private void openCamera(int cameraId) {
        Log.i("Scanner",
                "Device manufacturer: " + android.os.Build.MANUFACTURER + " model: " + android.os.Build.MODEL);

        camera = Camera.open(cameraId);
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, cameraInfo);

        Camera.Parameters parameters = camera.getParameters();
        List<String> focusModes = parameters.getSupportedFocusModes();
        if (//!android.os.Build.MANUFACTURER.equalsIgnoreCase("SAMSUNG") &&
        focusModes != null
                && !(this.experience != null && this.experience.getRequestedAutoFocusMode() != null
                        && (this.experience.getRequestedAutoFocusMode().equals("tapToFocus")
                                || this.experience.getRequestedAutoFocusMode().equals("blurScore"))
                        && focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO))
                && focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        } else if (focusModes != null && focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
            // if FOCUS_MODE_CONTINUOUS_VIDEO is not supported flag that manual auto-focus is needed every few seconds
            Log.w("Scanner", "Camera requires manual focusing");
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
            if (!(this.experience.getRequestedAutoFocusMode() != null
                    && this.experience.getRequestedAutoFocusMode().equals("blurScore"))) {
                deviceNeedsTapToFocus = true;
            }
        }

        float ratioOfSurface = (float) surfaceHeight / surfaceWidth;
        if (ratioOfSurface < 1) {
            ratioOfSurface = 1 / ratioOfSurface;
        }
        Log.i("Scanner",
                "Surface size: " + surfaceWidth + "x" + surfaceHeight + " (Ratio: " + ratioOfSurface + ")");
        Log.i("Scanner", "Format = " + parameters.getPictureFormat());

        // Step 2: Find scanner preview that is best match for estimated surface ratio
        final List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
        Camera.Size bestFitSoFar = null;
        float ratioDifferenceOfBestFitSoFar = 0;
        StringBuilder cameraPreviewSizeDebugMessage = new StringBuilder("Available camera preview sizes: ");
        for (Camera.Size supportedSize : supportedPreviewSizes) {
            float ratio = (float) supportedSize.width / supportedSize.height;
            float ratioDifference = Math.abs(ratio - ratioOfSurface);

            cameraPreviewSizeDebugMessage.append(supportedSize.width);
            cameraPreviewSizeDebugMessage.append('x');
            cameraPreviewSizeDebugMessage.append(supportedSize.height);
            cameraPreviewSizeDebugMessage.append(" (");
            cameraPreviewSizeDebugMessage.append(ratio);
            cameraPreviewSizeDebugMessage.append(") ");

            if (bestFitSoFar == null || ratioDifference < ratioDifferenceOfBestFitSoFar) {
                bestFitSoFar = supportedSize;
                ratioDifferenceOfBestFitSoFar = ratioDifference;
            }
        }
        Log.i("Scanner", cameraPreviewSizeDebugMessage.toString());

        if (bestFitSoFar != null) {
            // Would only be null if there are no supportedPreviewSizes
            this.debug_resolution = "Selected Preview Size: " + bestFitSoFar.width + "x" + bestFitSoFar.height
                    + " (" + ((float) bestFitSoFar.width / (float) bestFitSoFar.height) + ")";
            Log.i("Scanner", this.debug_resolution);
            parameters.setPreviewSize(bestFitSoFar.width, bestFitSoFar.height);

            camera.setParameters(parameters);

            info = new CameraInfo(cameraInfo, parameters, getDeviceRotation());
            camera.setDisplayOrientation(info.getRotation());
            setDetector(detector, experience);

            try {
                camera.setPreviewDisplay(surface);
            } catch (IOException e) {
                Log.w("Scanner", e.getMessage(), e);
            }

            camera.startPreview();
        }
    }

    private int getDeviceRotation() {
        WindowManager manager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        int rotation = manager.getDefaultDisplay().getRotation();
        switch (rotation) {
        case Surface.ROTATION_0:
            return 0;
        case Surface.ROTATION_90:
            return 90;
        case Surface.ROTATION_180:
            return 180;
        case Surface.ROTATION_270:
            return 270;
        }
        return 0;
    }

    private void startCamera() {
        if (cameraThread == null) {
            cameraThread = new HandlerThread(THREAD_NAME);
            cameraThread.start();
            Handler cameraHandler = new Handler(cameraThread.getLooper());
            cameraHandler.post(new Runnable() {
                @Override
                public void run() {
                    createCamera();
                }
            });
        } else {
            camera.startPreview();
        }
    }

    private void stopCamera() {
        if (camera != null) {
            camera.stopPreview();
            camera.setPreviewCallback(null);
            camera.release();
            if (cameraThread != null) {
                cameraThread.quit();
                cameraThread = null;
            }
            camera = null;
        }
    }

    /* Auto focus methods */

    public boolean deviceNeedsTapToFocus() {
        return this.deviceNeedsTapToFocus;
    }

    @Override
    public void focus(final Runnable callback) {

        synchronized (cameraFocusLock) {
            Log.i("FOCUS", "cameraIsFocused = false;");
            cameraIsFocused = false;
        }

        if (camera != null) {
            Log.i("FOCUS", "Attempting to focus camera.");
            camera.autoFocus(null);
        }

        // set a timeout rather than use the auto focus callback
        // (because the auto focus callback never gets called on some devices)
        Handler h = new Handler(Looper.getMainLooper());
        h.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i("FOCUS", "Firing auto focus timeout.");
                synchronized (cameraFocusLock) {
                    Log.i("FOCUS", "cameraIsFocused = true;");
                    cameraIsFocused = true;
                    Log.i("FOCUS", "notifyAll()");
                    cameraFocusLock.notifyAll();
                }
                callback.run();
            }
        }, 1000);
    }

}