Per Daniel -- don't check phone's preview size as we want to use screen size if possible
[zxing.git] / android / src / com / google / zxing / client / android / CameraManager.java
index 330b045..ee0c0fa 100755 (executable)
@@ -23,6 +23,7 @@ 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.util.Log;
@@ -31,6 +32,7 @@ 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
@@ -40,13 +42,18 @@ import java.io.IOException;
  * @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 final Pattern COMMA_PATTERN = Pattern.compile(",");
+
   private static CameraManager cameraManager;
+
   private Camera camera;
   private final Context context;
   private Point screenResolution;
@@ -60,6 +67,7 @@ final class CameraManager {
   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
@@ -67,6 +75,9 @@ final class CameraManager {
    */
   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);
@@ -115,6 +126,12 @@ final class CameraManager {
     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;
   }
 
   /**
@@ -126,6 +143,9 @@ final class CameraManager {
   public void openDriver(SurfaceHolder holder) throws IOException {
     if (camera == null) {
       camera = Camera.open();
+      if (camera == null) {
+        throw new IOException();
+      }
       camera.setPreviewDisplay(holder);
 
       if (!initialized) {
@@ -134,6 +154,7 @@ final class CameraManager {
       }
 
       setCameraParameters();
+      FlashlightManager.enableFlashlight();
     }
   }
 
@@ -142,6 +163,7 @@ final class CameraManager {
    */
   public void closeDriver() {
     if (camera != null) {
+      FlashlightManager.disableFlashlight();
       camera.release();
       camera = null;
     }
@@ -162,6 +184,9 @@ final class CameraManager {
    */
   public void stopPreview() {
     if (camera != null && previewing) {
+      if (!useOneShotPreviewCallback) {
+        camera.setPreviewCallback(null);
+      }
       camera.stopPreview();
       previewHandler = null;
       autoFocusHandler = null;
@@ -181,7 +206,11 @@ final class CameraManager {
     if (camera != null && previewing) {
       previewHandler = handler;
       previewMessage = message;
-      camera.setOneShotPreviewCallback(previewCallback);
+      if (useOneShotPreviewCallback) {
+        camera.setOneShotPreviewCallback(previewCallback);
+      } else {
+        camera.setPreviewCallback(previewCallback);
+      }
     }
   }
 
@@ -208,6 +237,9 @@ final class CameraManager {
    */
   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;
@@ -254,24 +286,29 @@ final class CameraManager {
    * @param data A preview frame.
    * @param width The width of the image.
    * @param height The height of the image.
-   * @return A BaseLuminanceSource subclass.
+   * @return A PlanarYUVLuminanceSource instance.
    */
-  public BaseLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
+  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:
-        // There's no PixelFormat constant for this buffer format yet.
-        if (previewFormatString.equals("yuv422i-yuyv")) {
-          return new InterleavedYUV422LuminanceSource(data, width, height, rect.left, rect.top,
-              rect.width(), rect.height());
+        // 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());
         }
-        break;
     }
-    return null;
+    throw new IllegalArgumentException("Unsupported picture format: " +
+        previewFormat + '/' + previewFormatString);
   }
 
   /**
@@ -282,18 +319,11 @@ final class CameraManager {
    */
   private void setCameraParameters() {
     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);
-
-    // 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, "Default preview format: " + previewFormat + '/' + previewFormatString);
+
+    cameraResolution = getCameraResolution(parameters);
     Log.v(TAG, "Setting preview size: " + cameraResolution.x + ", " + cameraResolution.y);
     parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
 
@@ -303,6 +333,13 @@ final class CameraManager {
     // This is the standard setting to turn the flash off that all devices should honor.
     parameters.set("flash-mode", "off");
 
+    // Set zoom to 2x if available. This helps encourage the user to pull back.
+    // Some devices like the Behold have a zoom parameter
+    parameters.set("zoom", "2.0");
+    // Most devices, like the Hero, appear to expose this zoom parameter.
+    // (I think) This means 2.0x
+    parameters.set("taking-picture-zoom", "20");
+
     camera.setParameters(parameters);
   }
 
@@ -315,4 +352,71 @@ final class CameraManager {
     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");
+        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");
+        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;
+  }
+
 }