From 4b3bfd8a8f16dad6a1413f75e68a5776e9e308b7 Mon Sep 17 00:00:00 2001 From: srowen Date: Wed, 7 Apr 2010 18:03:14 +0000 Subject: [PATCH] Issue 376: re-set camera params after first auto-focus callback to make it work on Droid. Along the way, did a very big reorganization of CameraManager since it was becoming spaghetti code git-svn-id: http://zxing.googlecode.com/svn/trunk@1292 59b500cc-1b3d-0410-9834-0bbf25fbcc57 --- .../zxing/client/android/CameraManager.java | 530 ------------------ .../zxing/client/android/CaptureActivity.java | 1 + .../android/CaptureActivityHandler.java | 3 + .../zxing/client/android/DecodeThread.java | 2 + .../android/PlanarYUVLuminanceSource.java | 2 +- .../client/android/PreferencesActivity.java | 14 +- .../zxing/client/android/ViewfinderView.java | 5 +- .../android/camera/AutoFocusCallback.java | 55 ++ .../camera/CameraConfigurationManager.java | 269 +++++++++ .../client/android/camera/CameraManager.java | 286 ++++++++++ .../{ => camera}/FlashlightManager.java | 2 +- .../android/camera/PreviewCallback.java | 54 ++ 12 files changed, 683 insertions(+), 540 deletions(-) delete mode 100755 android/src/com/google/zxing/client/android/CameraManager.java create mode 100644 android/src/com/google/zxing/client/android/camera/AutoFocusCallback.java create mode 100644 android/src/com/google/zxing/client/android/camera/CameraConfigurationManager.java create mode 100755 android/src/com/google/zxing/client/android/camera/CameraManager.java rename android/src/com/google/zxing/client/android/{ => camera}/FlashlightManager.java (99%) create mode 100644 android/src/com/google/zxing/client/android/camera/PreviewCallback.java diff --git a/android/src/com/google/zxing/client/android/CameraManager.java b/android/src/com/google/zxing/client/android/CameraManager.java deleted file mode 100755 index c18fc6d7..00000000 --- a/android/src/com/google/zxing/client/android/CameraManager.java +++ /dev/null @@ -1,530 +0,0 @@ -/* - * Copyright (C) 2008 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.client.android; - -import com.google.zxing.ResultPoint; - -import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.PixelFormat; -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.preference.PreferenceManager; -import android.util.Log; -import android.view.Display; -import android.view.SurfaceHolder; -import android.view.WindowManager; - -import java.io.IOException; -import java.util.regex.Pattern; - -/** - * 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.class.getSimpleName(); - - 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 final int TEN_DESIRED_ZOOM = 27; - private static final int DESIRED_SHARPNESS = 30; - - private static final Pattern COMMA_PATTERN = Pattern.compile(","); - - 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 final 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); - // Simulate continuous autofocus by sending a focus request every 1.5 seconds. - autoFocusHandler.sendMessageDelayed(message, 1500L); - autoFocusHandler = null; - } - } - }; - - /** - * 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 cameraManager; - } - - private CameraManager(Context context) { - 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. - useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > Build.VERSION_CODES.CUPCAKE; - } - - /** - * 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 void openDriver(SurfaceHolder holder) throws IOException { - if (camera == null) { - camera = Camera.open(); - if (camera == null) { - throw new IOException(); - } - camera.setPreviewDisplay(holder); - - if (!initialized) { - initialized = true; - getScreenResolution(); - } - - setCameraParameters(); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false)) { - FlashlightManager.enableFlashlight(); - } - } - } - - /** - * Closes the camera driver if still in use. - */ - public void closeDriver() { - if (camera != null) { - FlashlightManager.disableFlashlight(); - camera.release(); - camera = null; - } - } - - /** - * Asks the camera hardware to begin drawing preview frames to the screen. - */ - public void startPreview() { - if (camera != null && !previewing) { - camera.startPreview(); - previewing = true; - } - } - - /** - * Tells the camera to stop drawing preview frames. - */ - public void stopPreview() { - 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. - * - * @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 (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 (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. 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 (framingRect == null) { - if (camera == null) { - return 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; - } - - /** - * Converts the result points from still resolution coordinates to screen coordinates. - * - * @param points The points returned by the Reader subclass through Result.getResultPoints(). - * @return An array of Points scaled to the size of the framing rect and offset appropriately - * so they can be drawn in screen coordinates. - */ - public Point[] convertResultPoints(ResultPoint[] points) { - Rect frame = getFramingRect(); - int count = points.length; - Point[] output = new Point[count]; - for (int x = 0; x < count; x++) { - output[x] = new Point(); - output[x].x = frame.left + (int) (points[x].getX() + 0.5f); - output[x].y = frame.top + (int) (points[x].getY() + 0.5f); - } - return output; - } - - /** - * A factory method to build the appropriate LuminanceSource object based on the format - * of the preview buffers, as described by Camera.Parameters. - * - * @param data A preview frame. - * @param width The width of the image. - * @param height The height of the image. - * @return A PlanarYUVLuminanceSource instance. - */ - public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) { - Rect rect = getFramingRect(); - switch (previewFormat) { - // This is the standard Android format which all devices are REQUIRED to support. - // In theory, it's the only one we should ever care about. - case PixelFormat.YCbCr_420_SP: - // This format has never been seen in the wild, but is compatible as we only care - // about the Y channel, so allow it. - case PixelFormat.YCbCr_422_SP: - return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, - rect.width(), rect.height()); - default: - // The Samsung Moment incorrectly uses this variant instead of the 'sp' version. - // Fortunately, it too has all the Y data up front, so we can read it. - if ("yuv420p".equals(previewFormatString)) { - return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, - rect.width(), rect.height()); - } - } - throw new IllegalArgumentException("Unsupported picture format: " + - previewFormat + '/' + previewFormatString); - } - - /** - * 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 = camera.getParameters(); - previewFormat = parameters.getPreviewFormat(); - previewFormatString = parameters.get("preview-format"); - Log.v(TAG, "Default preview format: " + previewFormat + '/' + previewFormatString); - - cameraResolution = getCameraResolution(parameters); - Log.v(TAG, "Setting preview size: " + cameraResolution.x + ", " + cameraResolution.y); - parameters.setPreviewSize(cameraResolution.x, cameraResolution.y); - - setFlash(parameters); - setZoom(parameters); - //setSharpness(parameters); - - camera.setParameters(parameters); - } - - private Point getScreenResolution() { - if (screenResolution == null) { - WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - Display display = manager.getDefaultDisplay(); - screenResolution = new Point(display.getWidth(), display.getHeight()); - } - return screenResolution; - } - - private Point getCameraResolution(Camera.Parameters parameters) { - - String previewSizeValueString = parameters.get("preview-size-values"); - // saw this on Xperia - if (previewSizeValueString == null) { - previewSizeValueString = parameters.get("preview-size-value"); - } - - Point cameraResolution = null; - - if (previewSizeValueString != null) { - Log.v(TAG, "preview-size parameter: " + previewSizeValueString); - cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution); - } - - if (cameraResolution == null) { - // Ensure that the camera resolution is a multiple of 8, as the screen may not be. - cameraResolution = new Point( - (screenResolution.x >> 3) << 3, - (screenResolution.y >> 3) << 3); - } - - return cameraResolution; - } - - private static Point findBestPreviewSizeValue(String previewSizeValueString, Point screenResolution) { - int bestX = 0; - int bestY = 0; - int diff = Integer.MAX_VALUE; - for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) { - - previewSize = previewSize.trim(); - int dimPosition = previewSize.indexOf('x'); - if (dimPosition < 0) { - Log.w(TAG, "Bad preview-size: " + previewSize); - continue; - } - - int newX; - int newY; - try { - newX = Integer.parseInt(previewSize.substring(0, dimPosition)); - newY = Integer.parseInt(previewSize.substring(dimPosition + 1)); - } catch (NumberFormatException nfe) { - Log.w(TAG, "Bad preview-size: " + previewSize); - continue; - } - - int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y); - if (newDiff == 0) { - bestX = newX; - bestY = newY; - break; - } else if (newDiff < diff) { - bestX = newX; - bestY = newY; - diff = newDiff; - } - - } - - if (bestX > 0 && bestY > 0) { - return new Point(bestX, bestY); - } - return null; - } - - private static int findBestMotZoomValue(String stringValues, int tenDesiredZoom) { - int tenBestValue = 0; - for (String stringValue : COMMA_PATTERN.split(stringValues)) { - stringValue = stringValue.trim(); - double value; - try { - value = Double.parseDouble(stringValue); - } catch (NumberFormatException nfe) { - return tenDesiredZoom; - } - int tenValue = (int) (10.0 * value); - if (Math.abs(tenDesiredZoom - value) < Math.abs(tenDesiredZoom - tenBestValue)) { - tenBestValue = tenValue; - } - } - return tenBestValue; - } - - private void setFlash(Camera.Parameters parameters) { - // 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"); - } - - private void setZoom(Camera.Parameters parameters) { - - String zoomSupportedString = parameters.get("zoom-supported"); - if (zoomSupportedString != null && !Boolean.parseBoolean(zoomSupportedString)) { - return; - } - - int tenDesiredZoom = TEN_DESIRED_ZOOM; - - String maxZoomString = parameters.get("max-zoom"); - if (maxZoomString != null) { - try { - int tenMaxZoom = (int) (10.0 * Double.parseDouble(maxZoomString)); - if (tenDesiredZoom > tenMaxZoom) { - tenDesiredZoom = tenMaxZoom; - } - } catch (NumberFormatException nfe) { - Log.w(TAG, "Bad max-zoom: " + maxZoomString); - } - } - - String takingPictureZoomMaxString = parameters.get("taking-picture-zoom-max"); - if (takingPictureZoomMaxString != null) { - try { - int tenMaxZoom = Integer.parseInt(takingPictureZoomMaxString); - if (tenDesiredZoom > tenMaxZoom) { - tenDesiredZoom = tenMaxZoom; - } - } catch (NumberFormatException nfe) { - Log.w(TAG, "Bad taking-picture-zoom-max: " + takingPictureZoomMaxString); - } - } - - String motZoomValuesString = parameters.get("mot-zoom-values"); - if (motZoomValuesString != null) { - tenDesiredZoom = findBestMotZoomValue(motZoomValuesString, tenDesiredZoom); - } - - String motZoomStepString = parameters.get("mot-zoom-step"); - if (motZoomStepString != null) { - try { - double motZoomStep = Double.parseDouble(motZoomStepString.trim()); - int tenZoomStep = (int) (10.0 * motZoomStep); - if (tenZoomStep > 1) { - tenDesiredZoom -= tenDesiredZoom % tenZoomStep; - } - } catch (NumberFormatException nfe) { - // continue - } - } - - // Set zoom. This helps encourage the user to pull back. - // Some devices like the Behold have a zoom parameter - if (maxZoomString != null || motZoomValuesString != null) { - parameters.set("zoom", String.valueOf(tenDesiredZoom / 10.0)); - } - - // Most devices, like the Hero, appear to expose this zoom parameter. - // It takes on values like "27" which appears to mean 2.7x zoom - if (takingPictureZoomMaxString != null) { - parameters.set("taking-picture-zoom", tenDesiredZoom); - } - } - - /* - private void setSharpness(Camera.Parameters parameters) { - - int desiredSharpness = DESIRED_SHARPNESS; - - String maxSharpnessString = parameters.get("sharpness-max"); - if (maxSharpnessString != null) { - try { - int maxSharpness = Integer.parseInt(maxSharpnessString); - if (desiredSharpness > maxSharpness) { - desiredSharpness = maxSharpness; - } - } catch (NumberFormatException nfe) { - Log.w(TAG, "Bad sharpness-max: " + maxSharpnessString); - } - } - - parameters.set("sharpness", desiredSharpness); - } - */ -} diff --git a/android/src/com/google/zxing/client/android/CaptureActivity.java b/android/src/com/google/zxing/client/android/CaptureActivity.java index ba5ca285..9ad0093b 100755 --- a/android/src/com/google/zxing/client/android/CaptureActivity.java +++ b/android/src/com/google/zxing/client/android/CaptureActivity.java @@ -19,6 +19,7 @@ package com.google.zxing.client.android; import com.google.zxing.BarcodeFormat; import com.google.zxing.Result; import com.google.zxing.ResultPoint; +import com.google.zxing.client.android.camera.CameraManager; import com.google.zxing.client.android.history.HistoryManager; import com.google.zxing.client.android.result.ResultButtonListener; import com.google.zxing.client.android.result.ResultHandler; diff --git a/android/src/com/google/zxing/client/android/CaptureActivityHandler.java b/android/src/com/google/zxing/client/android/CaptureActivityHandler.java index 362881c4..a0d29e6c 100755 --- a/android/src/com/google/zxing/client/android/CaptureActivityHandler.java +++ b/android/src/com/google/zxing/client/android/CaptureActivityHandler.java @@ -18,6 +18,7 @@ package com.google.zxing.client.android; import com.google.zxing.BarcodeFormat; import com.google.zxing.Result; +import com.google.zxing.client.android.camera.CameraManager; import android.app.Activity; import android.content.Intent; @@ -35,6 +36,7 @@ import java.util.Vector; * @author dswitkin@google.com (Daniel Switkin) */ public final class CaptureActivityHandler extends Handler { + private final CaptureActivity activity; private final DecodeThread decodeThread; private State state; @@ -122,4 +124,5 @@ public final class CaptureActivityHandler extends Handler { activity.drawViewfinder(); } } + } diff --git a/android/src/com/google/zxing/client/android/DecodeThread.java b/android/src/com/google/zxing/client/android/DecodeThread.java index dce7d14c..7a407b1f 100755 --- a/android/src/com/google/zxing/client/android/DecodeThread.java +++ b/android/src/com/google/zxing/client/android/DecodeThread.java @@ -23,6 +23,7 @@ import com.google.zxing.MultiFormatReader; import com.google.zxing.ReaderException; import com.google.zxing.Result; import com.google.zxing.ResultPointCallback; +import com.google.zxing.client.android.camera.CameraManager; import com.google.zxing.common.HybridBinarizer; import android.content.SharedPreferences; @@ -141,4 +142,5 @@ final class DecodeThread extends Thread { message.sendToTarget(); } } + } diff --git a/android/src/com/google/zxing/client/android/PlanarYUVLuminanceSource.java b/android/src/com/google/zxing/client/android/PlanarYUVLuminanceSource.java index 549dfd5c..31cf7d96 100644 --- a/android/src/com/google/zxing/client/android/PlanarYUVLuminanceSource.java +++ b/android/src/com/google/zxing/client/android/PlanarYUVLuminanceSource.java @@ -37,7 +37,7 @@ public final class PlanarYUVLuminanceSource extends LuminanceSource { private final int left; private final int top; - PlanarYUVLuminanceSource(byte[] yuvData, int dataWidth, int dataHeight, int left, int top, + public PlanarYUVLuminanceSource(byte[] yuvData, int dataWidth, int dataHeight, int left, int top, int width, int height) { super(width, height); diff --git a/android/src/com/google/zxing/client/android/PreferencesActivity.java b/android/src/com/google/zxing/client/android/PreferencesActivity.java index 090953ab..d34e7ffa 100755 --- a/android/src/com/google/zxing/client/android/PreferencesActivity.java +++ b/android/src/com/google/zxing/client/android/PreferencesActivity.java @@ -31,16 +31,16 @@ import android.preference.PreferenceScreen; public final class PreferencesActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener { - static final String KEY_DECODE_1D = "preferences_decode_1D"; - static final String KEY_DECODE_QR = "preferences_decode_QR"; + public static final String KEY_DECODE_1D = "preferences_decode_1D"; + public static final String KEY_DECODE_QR = "preferences_decode_QR"; public static final String KEY_CUSTOM_PRODUCT_SEARCH = "preferences_custom_product_search"; - static final String KEY_PLAY_BEEP = "preferences_play_beep"; - static final String KEY_VIBRATE = "preferences_vibrate"; - static final String KEY_COPY_TO_CLIPBOARD = "preferences_copy_to_clipboard"; - static final String KEY_FRONT_LIGHT = "preferences_front_light"; + public static final String KEY_PLAY_BEEP = "preferences_play_beep"; + public static final String KEY_VIBRATE = "preferences_vibrate"; + public static final String KEY_COPY_TO_CLIPBOARD = "preferences_copy_to_clipboard"; + public static final String KEY_FRONT_LIGHT = "preferences_front_light"; - static final String KEY_HELP_VERSION_SHOWN = "preferences_help_version_shown"; + public static final String KEY_HELP_VERSION_SHOWN = "preferences_help_version_shown"; public static final String KEY_NOT_OUR_RESULTS_SHOWN = "preferences_not_out_results_shown"; private CheckBoxPreference decode1D; diff --git a/android/src/com/google/zxing/client/android/ViewfinderView.java b/android/src/com/google/zxing/client/android/ViewfinderView.java index b9720777..86960627 100755 --- a/android/src/com/google/zxing/client/android/ViewfinderView.java +++ b/android/src/com/google/zxing/client/android/ViewfinderView.java @@ -16,6 +16,9 @@ package com.google.zxing.client.android; +import com.google.zxing.ResultPoint; +import com.google.zxing.client.android.camera.CameraManager; + import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; @@ -24,7 +27,6 @@ import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; -import com.google.zxing.ResultPoint; import java.util.Collection; import java.util.HashSet; @@ -36,6 +38,7 @@ import java.util.HashSet; * @author dswitkin@google.com (Daniel Switkin) */ public final class ViewfinderView extends View { + private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64}; private static final long ANIMATION_DELAY = 100L; private static final int OPAQUE = 0xFF; diff --git a/android/src/com/google/zxing/client/android/camera/AutoFocusCallback.java b/android/src/com/google/zxing/client/android/camera/AutoFocusCallback.java new file mode 100644 index 00000000..fb3dd04a --- /dev/null +++ b/android/src/com/google/zxing/client/android/camera/AutoFocusCallback.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.client.android.camera; + +import android.hardware.Camera; +import android.os.Handler; +import android.os.Message; + +final class AutoFocusCallback implements Camera.AutoFocusCallback { + + private static final long AUTOFOCUS_INTERVAL_MS = 1500L; + + private final CameraConfigurationManager configManager; + private boolean reinitCamera; + private Handler autoFocusHandler; + private int autoFocusMessage; + + AutoFocusCallback(CameraConfigurationManager configManager) { + this.configManager = configManager; + } + + void setHandler(Handler autoFocusHandler, int autoFocusMessage) { + this.autoFocusHandler = autoFocusHandler; + this.autoFocusMessage = autoFocusMessage; + } + + public void onAutoFocus(boolean success, Camera camera) { + if (autoFocusHandler != null) { + Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success); + // Simulate continuous autofocus by sending a focus request every + // AUTOFOCUS_INTERVAL_MS milliseconds. + autoFocusHandler.sendMessageDelayed(message, AUTOFOCUS_INTERVAL_MS); + autoFocusHandler = null; + if (!reinitCamera) { + reinitCamera = true; + configManager.setDesiredCameraParameters(camera); + } + } + } + +} diff --git a/android/src/com/google/zxing/client/android/camera/CameraConfigurationManager.java b/android/src/com/google/zxing/client/android/camera/CameraConfigurationManager.java new file mode 100644 index 00000000..e70b1a31 --- /dev/null +++ b/android/src/com/google/zxing/client/android/camera/CameraConfigurationManager.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.client.android.camera; + +import java.util.regex.Pattern; + +import android.content.Context; +import android.graphics.Point; +import android.hardware.Camera; +import android.util.Log; +import android.view.Display; +import android.view.WindowManager; + +final class CameraConfigurationManager { + + private static final String TAG = CameraConfigurationManager.class.getSimpleName(); + + private static final int TEN_DESIRED_ZOOM = 27; + private static final int DESIRED_SHARPNESS = 30; + + private static final Pattern COMMA_PATTERN = Pattern.compile(","); + + private final Context context; + private Point screenResolution; + private Point cameraResolution; + private int previewFormat; + private String previewFormatString; + + CameraConfigurationManager(Context context) { + this.context = context; + } + + /** + * Reads, one time, values from the camera that are needed by the app. + */ + void initFromCameraParameters(Camera camera) { + Camera.Parameters parameters = camera.getParameters(); + previewFormat = parameters.getPreviewFormat(); + previewFormatString = parameters.get("preview-format"); + Log.v(TAG, "Default preview format: " + previewFormat + '/' + previewFormatString); + screenResolution = getScreenResolution(); + cameraResolution = getCameraResolution(parameters); + } + + /** + * 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. + */ + void setDesiredCameraParameters(Camera camera) { + Camera.Parameters parameters = camera.getParameters(); + Log.v(TAG, "Setting preview size: " + cameraResolution.x + ", " + cameraResolution.y); + parameters.setPreviewSize(cameraResolution.x, cameraResolution.y); + setFlash(parameters); + setZoom(parameters); + //setSharpness(parameters); + camera.setParameters(parameters); + } + + Point getCameraResolution() { + return cameraResolution; + } + + int getPreviewFormat() { + return previewFormat; + } + + String getPreviewFormatString() { + return previewFormatString; + } + + private Point getScreenResolution() { + WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = manager.getDefaultDisplay(); + return new Point(display.getWidth(), display.getHeight()); + } + + private Point getCameraResolution(Camera.Parameters parameters) { + + String previewSizeValueString = parameters.get("preview-size-values"); + // saw this on Xperia + if (previewSizeValueString == null) { + previewSizeValueString = parameters.get("preview-size-value"); + } + + Point cameraResolution = null; + + if (previewSizeValueString != null) { + Log.v(TAG, "preview-size parameter: " + previewSizeValueString); + cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution); + } + + if (cameraResolution == null) { + // Ensure that the camera resolution is a multiple of 8, as the screen may not be. + cameraResolution = new Point( + (screenResolution.x >> 3) << 3, + (screenResolution.y >> 3) << 3); + } + + return cameraResolution; + } + + private static Point findBestPreviewSizeValue(String previewSizeValueString, Point screenResolution) { + int bestX = 0; + int bestY = 0; + int diff = Integer.MAX_VALUE; + for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) { + + previewSize = previewSize.trim(); + int dimPosition = previewSize.indexOf('x'); + if (dimPosition < 0) { + Log.w(TAG, "Bad preview-size: " + previewSize); + continue; + } + + int newX; + int newY; + try { + newX = Integer.parseInt(previewSize.substring(0, dimPosition)); + newY = Integer.parseInt(previewSize.substring(dimPosition + 1)); + } catch (NumberFormatException nfe) { + Log.w(TAG, "Bad preview-size: " + previewSize); + continue; + } + + int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y); + if (newDiff == 0) { + bestX = newX; + bestY = newY; + break; + } else if (newDiff < diff) { + bestX = newX; + bestY = newY; + diff = newDiff; + } + + } + + if (bestX > 0 && bestY > 0) { + return new Point(bestX, bestY); + } + return null; + } + + private static int findBestMotZoomValue(String stringValues, int tenDesiredZoom) { + int tenBestValue = 0; + for (String stringValue : COMMA_PATTERN.split(stringValues)) { + stringValue = stringValue.trim(); + double value; + try { + value = Double.parseDouble(stringValue); + } catch (NumberFormatException nfe) { + return tenDesiredZoom; + } + int tenValue = (int) (10.0 * value); + if (Math.abs(tenDesiredZoom - value) < Math.abs(tenDesiredZoom - tenBestValue)) { + tenBestValue = tenValue; + } + } + return tenBestValue; + } + + private void setFlash(Camera.Parameters parameters) { + // 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"); + } + + private void setZoom(Camera.Parameters parameters) { + + String zoomSupportedString = parameters.get("zoom-supported"); + if (zoomSupportedString != null && !Boolean.parseBoolean(zoomSupportedString)) { + return; + } + + int tenDesiredZoom = TEN_DESIRED_ZOOM; + + String maxZoomString = parameters.get("max-zoom"); + if (maxZoomString != null) { + try { + int tenMaxZoom = (int) (10.0 * Double.parseDouble(maxZoomString)); + if (tenDesiredZoom > tenMaxZoom) { + tenDesiredZoom = tenMaxZoom; + } + } catch (NumberFormatException nfe) { + Log.w(TAG, "Bad max-zoom: " + maxZoomString); + } + } + + String takingPictureZoomMaxString = parameters.get("taking-picture-zoom-max"); + if (takingPictureZoomMaxString != null) { + try { + int tenMaxZoom = Integer.parseInt(takingPictureZoomMaxString); + if (tenDesiredZoom > tenMaxZoom) { + tenDesiredZoom = tenMaxZoom; + } + } catch (NumberFormatException nfe) { + Log.w(TAG, "Bad taking-picture-zoom-max: " + takingPictureZoomMaxString); + } + } + + String motZoomValuesString = parameters.get("mot-zoom-values"); + if (motZoomValuesString != null) { + tenDesiredZoom = findBestMotZoomValue(motZoomValuesString, tenDesiredZoom); + } + + String motZoomStepString = parameters.get("mot-zoom-step"); + if (motZoomStepString != null) { + try { + double motZoomStep = Double.parseDouble(motZoomStepString.trim()); + int tenZoomStep = (int) (10.0 * motZoomStep); + if (tenZoomStep > 1) { + tenDesiredZoom -= tenDesiredZoom % tenZoomStep; + } + } catch (NumberFormatException nfe) { + // continue + } + } + + // Set zoom. This helps encourage the user to pull back. + // Some devices like the Behold have a zoom parameter + if (maxZoomString != null || motZoomValuesString != null) { + parameters.set("zoom", String.valueOf(tenDesiredZoom / 10.0)); + } + + // Most devices, like the Hero, appear to expose this zoom parameter. + // It takes on values like "27" which appears to mean 2.7x zoom + if (takingPictureZoomMaxString != null) { + parameters.set("taking-picture-zoom", tenDesiredZoom); + } + } + + /* + private void setSharpness(Camera.Parameters parameters) { + + int desiredSharpness = DESIRED_SHARPNESS; + + String maxSharpnessString = parameters.get("sharpness-max"); + if (maxSharpnessString != null) { + try { + int maxSharpness = Integer.parseInt(maxSharpnessString); + if (desiredSharpness > maxSharpness) { + desiredSharpness = maxSharpness; + } + } catch (NumberFormatException nfe) { + Log.w(TAG, "Bad sharpness-max: " + maxSharpnessString); + } + } + + parameters.set("sharpness", desiredSharpness); + } + */ +} diff --git a/android/src/com/google/zxing/client/android/camera/CameraManager.java b/android/src/com/google/zxing/client/android/camera/CameraManager.java new file mode 100755 index 00000000..5f674718 --- /dev/null +++ b/android/src/com/google/zxing/client/android/camera/CameraManager.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.client.android.camera; + +import com.google.zxing.ResultPoint; + +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.Camera; +import android.os.Build; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.SurfaceHolder; +import com.google.zxing.client.android.PlanarYUVLuminanceSource; +import com.google.zxing.client.android.PreferencesActivity; + +import java.io.IOException; + +/** + * 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) + */ +public final class CameraManager { + + private static final String TAG = CameraManager.class.getSimpleName(); + + 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 final Context context; + private final CameraConfigurationManager configManager; + private Camera camera; + private Rect framingRect; + private boolean initialized; + private boolean previewing; + private final 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 PreviewCallback previewCallback; + /** Autofocus callbacks arrive here, and are dispatched to the Handler which requested them. */ + private final AutoFocusCallback autoFocusCallback; + + /** + * 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 cameraManager; + } + + private CameraManager(Context context) { + + this.context = context; + this.configManager = new CameraConfigurationManager(context); + + // 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. + useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > Build.VERSION_CODES.CUPCAKE; + + previewCallback = new PreviewCallback(configManager, useOneShotPreviewCallback); + autoFocusCallback = new AutoFocusCallback(configManager); + } + + /** + * 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 void openDriver(SurfaceHolder holder) throws IOException { + if (camera == null) { + camera = Camera.open(); + if (camera == null) { + throw new IOException(); + } + camera.setPreviewDisplay(holder); + + if (!initialized) { + initialized = true; + configManager.initFromCameraParameters(camera); + } + configManager.setDesiredCameraParameters(camera); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false)) { + FlashlightManager.enableFlashlight(); + } + } + } + + /** + * Closes the camera driver if still in use. + */ + public void closeDriver() { + if (camera != null) { + FlashlightManager.disableFlashlight(); + camera.release(); + camera = null; + } + } + + /** + * Asks the camera hardware to begin drawing preview frames to the screen. + */ + public void startPreview() { + if (camera != null && !previewing) { + camera.startPreview(); + previewing = true; + } + } + + /** + * Tells the camera to stop drawing preview frames. + */ + public void stopPreview() { + if (camera != null && previewing) { + if (!useOneShotPreviewCallback) { + camera.setPreviewCallback(null); + } + camera.stopPreview(); + previewCallback.setHandler(null, 0); + autoFocusCallback.setHandler(null, 0); + 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. + * + * @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 (camera != null && previewing) { + previewCallback.setHandler(handler, 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 (camera != null && previewing) { + autoFocusCallback.setHandler(handler, message); + camera.autoFocus(autoFocusCallback); + } + } + + /** + * Calculates the framing rect which the UI should draw to show the user where to place the + * 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() { + Point cameraResolution = configManager.getCameraResolution(); + if (framingRect == null) { + if (camera == null) { + return 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; + } + + /** + * Converts the result points from still resolution coordinates to screen coordinates. + * + * @param points The points returned by the Reader subclass through Result.getResultPoints(). + * @return An array of Points scaled to the size of the framing rect and offset appropriately + * so they can be drawn in screen coordinates. + */ + public Point[] convertResultPoints(ResultPoint[] points) { + Rect frame = getFramingRect(); + int count = points.length; + Point[] output = new Point[count]; + for (int x = 0; x < count; x++) { + output[x] = new Point(); + output[x].x = frame.left + (int) (points[x].getX() + 0.5f); + output[x].y = frame.top + (int) (points[x].getY() + 0.5f); + } + return output; + } + + /** + * A factory method to build the appropriate LuminanceSource object based on the format + * of the preview buffers, as described by Camera.Parameters. + * + * @param data A preview frame. + * @param width The width of the image. + * @param height The height of the image. + * @return A PlanarYUVLuminanceSource instance. + */ + public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) { + Rect rect = getFramingRect(); + int previewFormat = configManager.getPreviewFormat(); + String previewFormatString = configManager.getPreviewFormatString(); + switch (previewFormat) { + // This is the standard Android format which all devices are REQUIRED to support. + // In theory, it's the only one we should ever care about. + case PixelFormat.YCbCr_420_SP: + // This format has never been seen in the wild, but is compatible as we only care + // about the Y channel, so allow it. + case PixelFormat.YCbCr_422_SP: + return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, + rect.width(), rect.height()); + default: + // The Samsung Moment incorrectly uses this variant instead of the 'sp' version. + // Fortunately, it too has all the Y data up front, so we can read it. + if ("yuv420p".equals(previewFormatString)) { + return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, + rect.width(), rect.height()); + } + } + throw new IllegalArgumentException("Unsupported picture format: " + + previewFormat + '/' + previewFormatString); + } + +} diff --git a/android/src/com/google/zxing/client/android/FlashlightManager.java b/android/src/com/google/zxing/client/android/camera/FlashlightManager.java similarity index 99% rename from android/src/com/google/zxing/client/android/FlashlightManager.java rename to android/src/com/google/zxing/client/android/camera/FlashlightManager.java index 894d751a..44459d19 100644 --- a/android/src/com/google/zxing/client/android/FlashlightManager.java +++ b/android/src/com/google/zxing/client/android/camera/FlashlightManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.zxing.client.android; +package com.google.zxing.client.android.camera; import android.os.IBinder; import android.util.Log; diff --git a/android/src/com/google/zxing/client/android/camera/PreviewCallback.java b/android/src/com/google/zxing/client/android/camera/PreviewCallback.java new file mode 100644 index 00000000..83377bab --- /dev/null +++ b/android/src/com/google/zxing/client/android/camera/PreviewCallback.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.client.android.camera; + +import android.graphics.Point; +import android.hardware.Camera; +import android.os.Handler; +import android.os.Message; + +final class PreviewCallback implements Camera.PreviewCallback { + + private final CameraConfigurationManager configManager; + private final boolean useOneShotPreviewCallback; + private Handler previewHandler; + private int previewMessage; + + PreviewCallback(CameraConfigurationManager configManager, boolean useOneShotPreviewCallback) { + this.configManager = configManager; + this.useOneShotPreviewCallback = useOneShotPreviewCallback; + } + + void setHandler(Handler previewHandler, int previewMessage) { + this.previewHandler = previewHandler; + this.previewMessage = previewMessage; + } + + public void onPreviewFrame(byte[] data, Camera camera) { + Point cameraResolution = configManager.getCameraResolution(); + if (!useOneShotPreviewCallback) { + camera.setPreviewCallback(null); + } + if (previewHandler != null) { + Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x, + cameraResolution.y, data); + message.sendToTarget(); + previewHandler = null; + } + } + +} -- 2.20.1