Reputation: 741
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
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