import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
+import android.os.Build;
import android.os.Handler;
import android.os.Message;
+import android.util.Log;
import android.view.Display;
import android.view.SurfaceHolder;
import android.view.WindowManager;
* This object wraps the Camera service object and expects to be the only one talking to it. The
* implementation encapsulates the steps needed to take preview-sized images, which are used for
* both preview and decoding.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
*/
final class CameraManager {
-
private static final String TAG = "CameraManager";
+ private static final int MIN_FRAME_WIDTH = 240;
+ private static final int MIN_FRAME_HEIGHT = 240;
+ private static final int MAX_FRAME_WIDTH = 480;
+ private static final int MAX_FRAME_HEIGHT = 360;
+
+ private static CameraManager cameraManager;
+ private Camera camera;
+ private final Context context;
+ private Point screenResolution;
+ private Point cameraResolution;
+ private Rect framingRect;
+ private Handler previewHandler;
+ private int previewMessage;
+ private Handler autoFocusHandler;
+ private int autoFocusMessage;
+ private boolean initialized;
+ private boolean previewing;
+ private int previewFormat;
+ private String previewFormatString;
+ private boolean useOneShotPreviewCallback;
+
+ /**
+ * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
+ * clear the handler so it will only receive one message.
+ */
+ private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
+ public void onPreviewFrame(byte[] data, Camera camera) {
+ if (!useOneShotPreviewCallback) {
+ camera.setPreviewCallback(null);
+ }
+ if (previewHandler != null) {
+ Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,
+ cameraResolution.y, data);
+ message.sendToTarget();
+ previewHandler = null;
+ }
+ }
+ };
+
+ /**
+ * Autofocus callbacks arrive here, and are dispatched to the Handler which requested them.
+ */
+ private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
+ public void onAutoFocus(boolean success, Camera camera) {
+ if (autoFocusHandler != null) {
+ Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success);
+ // Barcode Scanner needs to insert a delay here because it does continuous focus,
+ // but this test app does not, so send the message immediately.
+ message.sendToTarget();
+ autoFocusHandler = null;
+ }
+ }
+ };
- private static CameraManager mCameraManager;
- private Camera mCamera;
- private final Context mContext;
- private Point mScreenResolution;
- private Rect mFramingRect;
- private Handler mPreviewHandler;
- private int mPreviewMessage;
- private Handler mAutoFocusHandler;
- private int mAutoFocusMessage;
- private boolean mPreviewing;
-
- public static synchronized void init(Context context) {
- if (mCameraManager == null) {
- mCameraManager = new CameraManager(context);
- mCameraManager.getScreenResolution();
+ /**
+ * Initializes this static object with the Context of the calling Activity.
+ *
+ * @param context The Activity which wants to use the camera.
+ */
+ public static void init(Context context) {
+ if (cameraManager == null) {
+ cameraManager = new CameraManager(context);
}
}
+ /**
+ * Gets the CameraManager singleton instance.
+ *
+ * @return A reference to the CameraManager singleton.
+ */
public static CameraManager get() {
- return mCameraManager;
+ return cameraManager;
}
private CameraManager(Context context) {
- mContext = context;
- mCamera = null;
- mPreviewing = false;
+ this.context = context;
+ camera = null;
+ initialized = false;
+ previewing = false;
+
+ // Camera.setOneShotPreviewCallback() has a race condition in Cupcake, so we use the older
+ // Camera.setPreviewCallback() on 1.5 and earlier. For Donut and later, we need to use
+ // the more efficient one shot callback, as the older one can swamp the system and cause it
+ // to run out of memory. We can't use SDK_INT because it was introduced in the Donut SDK.
+ if (Integer.parseInt(Build.VERSION.SDK) <= Build.VERSION_CODES.CUPCAKE) {
+ useOneShotPreviewCallback = false;
+ } else {
+ useOneShotPreviewCallback = true;
+ }
}
- // Throws IOException added to accommodate Android 1.5.
+ /**
+ * Opens the camera driver and initializes the hardware parameters.
+ *
+ * @param holder The surface object which the camera will draw preview frames into.
+ * @throws IOException Indicates the camera driver failed to open.
+ */
public String openDriver(SurfaceHolder holder, boolean getParameters) throws IOException {
String result = null;
- if (mCamera == null) {
- mCamera = Camera.open();
- mCamera.setPreviewDisplay(holder);
+ if (camera == null) {
+ camera = Camera.open();
+ camera.setPreviewDisplay(holder);
+
+ if (!initialized) {
+ initialized = true;
+ getScreenResolution();
+ }
+
if (getParameters) {
result = collectCameraParameters();
}
return result;
}
+ /**
+ * Closes the camera driver if still in use.
+ */
public void closeDriver() {
- if (mCamera != null) {
- mCamera.release();
- mCamera = null;
+ if (camera != null) {
+ camera.release();
+ camera = null;
}
}
+ /**
+ * Asks the camera hardware to begin drawing preview frames to the screen.
+ */
public void startPreview() {
- if (mCamera != null && !mPreviewing) {
- mCamera.startPreview();
- mPreviewing = true;
+ if (camera != null && !previewing) {
+ camera.startPreview();
+ previewing = true;
}
}
+ /**
+ * Tells the camera to stop drawing preview frames.
+ */
public void stopPreview() {
- if (mCamera != null && mPreviewing) {
- mCamera.setPreviewCallback(null);
- mCamera.stopPreview();
- mPreviewHandler = null;
- mAutoFocusHandler = null;
- mPreviewing = false;
+ if (camera != null && previewing) {
+ if (!useOneShotPreviewCallback) {
+ camera.setPreviewCallback(null);
+ }
+ camera.stopPreview();
+ previewHandler = null;
+ autoFocusHandler = null;
+ previewing = false;
}
}
/**
- * A single preview frame will be returned to the handler supplied. The data will arrive as
- * byte[] in the message.obj field, with width and height encoded as message.arg1 and
- * message.arg2, respectively.
+ * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
+ * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
+ * respectively.
*
* @param handler The handler to send the message to.
* @param message The what field of the message to be sent.
*/
public void requestPreviewFrame(Handler handler, int message) {
- if (mCamera != null && mPreviewing) {
- mPreviewHandler = handler;
- mPreviewMessage = message;
- mCamera.setPreviewCallback(previewCallback);
+ if (camera != null && previewing) {
+ previewHandler = handler;
+ previewMessage = message;
+ if (useOneShotPreviewCallback) {
+ camera.setOneShotPreviewCallback(previewCallback);
+ } else {
+ camera.setPreviewCallback(previewCallback);
+ }
}
}
+ /**
+ * Asks the camera hardware to perform an autofocus.
+ *
+ * @param handler The Handler to notify when the autofocus completes.
+ * @param message The message to deliver.
+ */
public void requestAutoFocus(Handler handler, int message) {
- if (mCamera != null && mPreviewing) {
- mAutoFocusHandler = handler;
- mAutoFocusMessage = message;
- mCamera.autoFocus(autoFocusCallback);
+ if (camera != null && previewing) {
+ autoFocusHandler = handler;
+ autoFocusMessage = message;
+ camera.autoFocus(autoFocusCallback);
}
}
/**
* Calculates the framing rect which the UI should draw to show the user where to place the
- * barcode. The actual captured image should be a bit larger than indicated because they might
- * frame the shot too tightly. This target helps with alignment as well as forces the user to
- * hold the device far enough away to ensure the image will be in focus.
+ * barcode. This target helps with alignment as well as forces the user to hold the device
+ * far enough away to ensure the image will be in focus.
*
* @return The rectangle to draw on screen in window coordinates.
*/
public Rect getFramingRect() {
- if (mFramingRect == null) {
- int width = mScreenResolution.x * 3 / 4;
- int height = mScreenResolution.y * 3 / 4;
- int leftOffset = (mScreenResolution.x - width) / 2;
- int topOffset = (mScreenResolution.y - height) / 2;
- mFramingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
- }
- return mFramingRect;
- }
-
- /**
- * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
- * clear the handler so it will only receive one message.
- */
- private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
- public void onPreviewFrame(byte[] data, Camera camera) {
- if (mPreviewHandler != null) {
- mCamera.setPreviewCallback(null);
- Message message = mPreviewHandler.obtainMessage(mPreviewMessage,
- mScreenResolution.x, mScreenResolution.y, data);
- message.sendToTarget();
- mPreviewHandler = null;
+ if (framingRect == null) {
+ if (camera == null) {
+ return null;
}
- }
- };
-
- private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
- public void onAutoFocus(boolean success, Camera camera) {
- if (mAutoFocusHandler != null) {
- Message message = mAutoFocusHandler.obtainMessage(mAutoFocusMessage, success);
- // The Barcodes app needs to insert a delay here because it does continuous focus,
- // but this test app does not, so send the message immediately.
- message.sendToTarget();
- mAutoFocusHandler = null;
+ int width = cameraResolution.x * 3 / 4;
+ if (width < MIN_FRAME_WIDTH) {
+ width = MIN_FRAME_WIDTH;
+ } else if (width > MAX_FRAME_WIDTH) {
+ width = MAX_FRAME_WIDTH;
+ }
+ int height = cameraResolution.y * 3 / 4;
+ if (height < MIN_FRAME_HEIGHT) {
+ height = MIN_FRAME_HEIGHT;
+ } else if (height > MAX_FRAME_HEIGHT) {
+ height = MAX_FRAME_HEIGHT;
}
+ int leftOffset = (cameraResolution.x - width) / 2;
+ int topOffset = (cameraResolution.y - height) / 2;
+ framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
+ Log.v(TAG, "Calculated framing rect: " + framingRect);
}
- };
+ return framingRect;
+ }
/**
- * Sets the camera up to take preview images which are used for both preview and decoding. We're
- * counting on the default YUV420 semi-planar data. If that changes in the future, we'll need to
- * specify it explicitly with setPreviewFormat().
+ * Sets the camera up to take preview images which are used for both preview and decoding.
+ * We detect the preview format here so that buildLuminanceSource() can build an appropriate
+ * LuminanceSource subclass. In the future we may want to force YUV420SP as it's the smallest,
+ * and the planar Y can be used for barcode scanning without a copy in some cases.
*/
private void setCameraParameters() {
- Camera.Parameters parameters = mCamera.getParameters();
- parameters.setPreviewSize(mScreenResolution.x, mScreenResolution.y);
- mCamera.setParameters(parameters);
+ Camera.Parameters parameters = camera.getParameters();
+ Camera.Size size = parameters.getPreviewSize();
+ Log.v(TAG, "Default preview size: " + size.width + ", " + size.height);
+ previewFormat = parameters.getPreviewFormat();
+ previewFormatString = parameters.get("preview-format");
+ Log.v(TAG, "Default preview format: " + previewFormat + '/' + previewFormatString);
+
+ // Ensure that the camera resolution is a multiple of 8, as the screen may not be.
+ // TODO: A better solution would be to request the supported preview resolutions
+ // and pick the best match, but this parameter is not standardized in Cupcake.
+ cameraResolution = new Point();
+ cameraResolution.x = (screenResolution.x >> 3) << 3;
+ cameraResolution.y = (screenResolution.y >> 3) << 3;
+ Log.v(TAG, "Setting preview size: " + cameraResolution.x + ", " + cameraResolution.y);
+ parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
+
+ // FIXME: This is a hack to turn the flash off on the Samsung Galaxy.
+ parameters.set("flash-value", 2);
+
+ // This is the standard setting to turn the flash off that all devices should honor.
+ parameters.set("flash-mode", "off");
+
+ camera.setParameters(parameters);
}
private String collectCameraParameters() {
- Camera.Parameters parameters = mCamera.getParameters();
+ Camera.Parameters parameters = camera.getParameters();
String[] params = parameters.flatten().split(";");
StringBuffer result = new StringBuffer();
result.append("Default camera parameters:");
}
private Point getScreenResolution() {
- if (mScreenResolution == null) {
- WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- Display display = wm.getDefaultDisplay();
- mScreenResolution = new Point(display.getWidth(), display.getHeight());
+ if (screenResolution == null) {
+ WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = manager.getDefaultDisplay();
+ screenResolution = new Point(display.getWidth(), display.getHeight());
}
- return mScreenResolution;
+ return screenResolution;
}
-
}