Raging Software
Raging Software

Reputation: 741

Android Camera.takePicture failed

As a bit of a learning exercise, I'm writing a security app that, when an arbitrary event happens, needs to turn the camera on, take a picture, and turn the camera off, without worrying about flash, focus, or displaying a preview. I followed along the online demos and made a working app that takes a picture, but it uses previews and all that. So I started working on getting it to work without a preview. Anyway, I keep getting 'takePicture failed' exceptions, and I have no earthly idea why. I was hoping someone with more experience with the Camera API could take a look and point me in the direction of a solution. Below are my pertinent files. I'm using the latest Android Studio and testing on Galaxy S4.

[MainActivity.java]

package com.g5digital.cam2;

import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;

import java.io.IOException;

public class MainActivity extends ActionBarActivity implements View.OnClickListener {

    private static final String TAG = "MainActivity";
    private Button button;
    private int cameraId;
    private Camera camera;
    private CameraPreview camPreview;
    private LinearLayout container;
    private Camera.Parameters camParms;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        container = (LinearLayout)findViewById(R.id.container);

        button = (Button)findViewById(R.id.button);
        button.setOnClickListener(this);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onClick(View view) {
        openCamera();

        try {
            if (camera != null) {
                PhotoHandler ph = new PhotoHandler(this, camera);
                camera.takePicture(null, null, ph);

                // Commented until takePicture() works
                /*(new Handler()).postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        MainActivity.this.closeCamera();
                    }
                }, 1000);*/
            }
        }
        catch (Exception e) {
            closeCamera();
            Log.d(TAG, e.getMessage());
            e.printStackTrace();
        }
    }

    private void openCamera() {
        // do we have a camera?
        if (!getPackageManager()
                .hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
            Toast.makeText(this, "No camera on this device", Toast.LENGTH_LONG)
                    .show();
        }
        else {
            closeCamera();

            cameraId = findFrontFacingCameraId();
            if (cameraId < 0) {
                Toast.makeText(this, "No front facing camera found.",
                        Toast.LENGTH_LONG).show();
            } else {
                camera = Camera.open(cameraId);
                try {
                    setCamParms();

                    setCamPreview();
                    camera.startPreview();
                }
                catch (Exception e) {
                    closeCamera();
                    e.printStackTrace();
                    finish();
                    return;
                }
            }
        }
    }

    private void closeCamera() {
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }

    private int findFrontFacingCameraId() {
        int camera_id = -1;
        // Search for the front facing camera
        int numberOfCameras = Camera.getNumberOfCameras();
        for (int i = 0; i < numberOfCameras; i++) {
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(i, info);
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                Log.d(TAG, "Camera found");
                camera_id = i;
                break;
            }
        }
        return camera_id;
    }

    private void setCamParms() {
        if (camParms == null && camera != null) {
            camParms = camera.getParameters();
            camParms.setFlashMode("Off");
        }
        if (camera != null) {
            camera.setParameters(camParms);
            camera.setDisplayOrientation(90);
        }
    }

    private void setCamPreview() throws IOException {
        if (camPreview == null && camera != null) {
            camPreview = new CameraPreview(this, camera);
        }
        if (camera != null) {
            camera.setPreviewDisplay(camPreview.getHolder());
        }
    }
}

[CameraPreview.java]

package com.g5digital.cam2;

import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.io.IOException;

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private Context context;
    private Camera camera;
    private SurfaceHolder holder;
    private static final String TAG = "CameraPreview";

    public CameraPreview(Context c, Camera cam) {
        super(c);
        context = c;
        camera = cam;
        holder = getHolder();
        holder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        try {
            camera.setPreviewDisplay(holder);
            camera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        } catch (Exception e) {
            // Probably getting "called after release()" message
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i2, int i3) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (holder.getSurface() == null){
            // preview surface does not exist
            return;
        }

        // stop preview before making changes
        try {
            camera.stopPreview();
        } catch (Exception e){
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            camera.setPreviewDisplay(holder);
            camera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        //
    }
}

[PhotoHandler.java]

package com.g5digital.cam2;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.hardware.Camera;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class PhotoHandler implements Camera.PictureCallback {
    private final Context context;
    private final Camera camera;

    public PhotoHandler(Context context, Camera c) {
        this.context = context;
        this.camera = c;
    }

    @Override
    public void onPictureTaken(byte[] bytes, Camera cam) {
        Log.i("PhotoHandler", "Picture taken!");
        File pictureFileDir = getDir();

        if (!pictureFileDir.exists() && !pictureFileDir.mkdirs()) {

            Log.d("PhotoHandler", "Can't create directory to save image.");
            Toast.makeText(context, "Can't create directory to save image.",
                    Toast.LENGTH_LONG).show();
            return;

        }

        Bitmap bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
        int width = bmp.getWidth();
        int height = bmp.getHeight();
        Matrix matrix = new Matrix();
        matrix.postRotate(270);
        Bitmap rotatedBitmap = Bitmap.createBitmap(bmp, 0, 0,
                width, height, matrix, true);

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String date = dateFormat.format(new Date());
        String photoFile = "Picture_" + date + ".jpg";

        String filename = pictureFileDir.getPath() + File.separator + photoFile;

        File pictureFile = new File(filename);

        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            boolean result = rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos);
            //fos.write(bytes);
            fos.close();
            if (result) {
                Toast.makeText(context, "New Image saved:" + photoFile,
                        Toast.LENGTH_LONG).show();
            }
            else {
                Toast.makeText(context, "Couldn't save image:" + photoFile,
                        Toast.LENGTH_LONG).show();
            }
            camera.startPreview();
        } catch (Exception error) {
            Log.d("PhotoHandler", "File" + filename + "not saved: "
                    + error.getMessage());
            Toast.makeText(context, "Image could not be saved.",
                    Toast.LENGTH_LONG).show();
        }
    }

    private File getDir() {
        File sdDir = Environment
                .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        return new File(sdDir, "CameraAPIDemo");
    }
}

[AndroidManifest.xml]

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.g5digital.cam2" >

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.FLASHLIGHT" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.g5digital.cam2.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

[activity_main.xml]

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.g5digital.cam2.MainActivity"
    tools:ignore="MergeRootFrame">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/click"
        android:id="@+id/button" />
</LinearLayout>

[LogCat output]

01-29 14:55:45.826    5853-5853/com.g5digital.cam2 D/MainActivity﹕ takePicture failed
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ java.lang.RuntimeException: takePicture failed
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at android.hardware.Camera.native_takePicture(Native Method)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at android.hardware.Camera.takePicture(Camera.java:1194)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at android.hardware.Camera.takePicture(Camera.java:1139)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at com.g5digital.cam2.MainActivity.onClick(MainActivity.java:66)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at android.view.View.performClick(View.java:4475)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at android.view.View$PerformClick.run(View.java:18786)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at android.os.Handler.handleCallback(Handler.java:730)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at android.os.Handler.dispatchMessage(Handler.java:92)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at android.os.Looper.loop(Looper.java:137)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at android.app.ActivityThread.main(ActivityThread.java:5419)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at java.lang.reflect.Method.invokeNative(Native Method)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at java.lang.reflect.Method.invoke(Method.java:525)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1187)
01-29 14:55:45.826    5853-5853/com.g5digital.cam2 W/System.err﹕ at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
01-29 14:55:45.836    5853-5853/com.g5digital.cam2 W/System.err﹕ at dalvik.system.NativeStart.main(Native Method)

Upvotes: 4

Views: 7440

Answers (1)

Alex Cohn
Alex Cohn

Reputation: 57203

No, to take picture you must show preview. There are quite a few workarounds invented by skillful people, see e.g. Take Picture without preview Android, Android: Is it possible to take a picture with the camera from a service with no UI, How to use Camera to take picture in a background Service on Android?...

But keep in mind that the requirement is not for technical, but privacy purposes. And the system continues to evolve and protect against newly found workarounds.

Maybe the most robust way to hide preview on S4 is to use SurfaceTexture, but display it invisibly, e.g. shifted off viewport.

Upvotes: 3

Related Questions