/** * 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; } }