/**
* Copyright (c) 2012 Vinayak Solutions Private Limited
* See the file license.txt for copying permission.
*/
package com.vinsol.expensetracker.cameraservice;
import java.io.IOException;
import android.hardware.Camera.Parameters;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import com.vinsol.expensetracker.utils.Log;
public class CameraHolder {
private android.hardware.Camera mCameraDevice;
private long mKeepBeforeTime = 0; // Keep the Camera before this time.
private final Handler mHandler;
private int mUsers = 0; // number of open() - number of release()
// We store the camera parameters when we actually open the device,
// so we can restore them in the subsequent open() requests by the user.
// This prevents the parameters set by the Camera activity used by
// the VideoCamera activity inadvertently.
private Parameters mParameters;
// Use a singleton.
private static CameraHolder sHolder;
public static synchronized CameraHolder instance() {
if (sHolder == null) {
sHolder = new CameraHolder();
}
return sHolder;
}
private static final int RELEASE_CAMERA = 1;
private class MyHandler extends Handler {
MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case RELEASE_CAMERA:
synchronized (CameraHolder.this) {
// In 'CameraHolder.open', the 'RELEASE_CAMERA' message
// will be removed if it is found in the queue. However,
// there is a chance that this message has been handled
// before being removed. So, we need to add a check
// here:
if (CameraHolder.this.mUsers == 0) releaseCamera();
}
break;
}
}
}
private CameraHolder() {
HandlerThread ht = new HandlerThread("CameraHolder");
ht.start();
mHandler = new MyHandler(ht.getLooper());
}
public void Assert(boolean cond) {
if (!cond) {
throw new AssertionError();
}
}
public synchronized android.hardware.Camera open()
throws CameraHardwareException {
Assert(mUsers == 0);
if (mCameraDevice == null) {
try {
mCameraDevice = android.hardware.Camera.open();
} catch (RuntimeException e) {
Log.d("fail to connect Camera"+ e);
throw new CameraHardwareException(e);
}
mParameters = mCameraDevice.getParameters();
} else {
try {
mCameraDevice.reconnect();
} catch (IOException e) {
Log.d("reconnect failed.");
throw new CameraHardwareException(e);
}
mCameraDevice.setParameters(mParameters);
}
++mUsers;
mHandler.removeMessages(RELEASE_CAMERA);
mKeepBeforeTime = 0;
return mCameraDevice;
}
/**
* Tries to open the hardware camera. If the camera is being used or
* unavailable then return {@code null}.
*/
public synchronized android.hardware.Camera tryOpen() {
try {
return mUsers == 0 ? open() : null;
} catch (CameraHardwareException e) {
// In eng build, we throw the exception so that test tool
// can detect it and report it
if ("eng".equals(Build.TYPE)) {
throw new RuntimeException(e);
}
return null;
}
}
public synchronized void release() {
Assert(mUsers == 1);
--mUsers;
mCameraDevice.stopPreview();
releaseCamera();
}
private synchronized void releaseCamera() {
Assert(mUsers == 0);
Assert(mCameraDevice != null);
long now = System.currentTimeMillis();
if (now < mKeepBeforeTime) {
mHandler.sendEmptyMessageDelayed(RELEASE_CAMERA,
mKeepBeforeTime - now);
return;
}
mCameraDevice.release();
mCameraDevice = null;
}
public synchronized void keep() {
// We allow (mUsers == 0) for the convenience of the calling activity.
// The activity may not have a chance to call open() before the user
// choose the menu item to switch to another activity.
Assert(mUsers == 1 || mUsers == 0);
// Keep the camera instance for 3 seconds.
mKeepBeforeTime = System.currentTimeMillis() + 3000;
}
}